{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} module OpenTelemetry.Propagator.W3CTraceContextSpec (spec) where import Data.Attoparsec.ByteString (parseOnly) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as C8 import Data.Either (isLeft) import qualified Data.Text as T import OpenTelemetry.Propagator.W3CTraceContext import OpenTelemetry.Trace.TraceState (Key (..), Value (..), empty, fromList, insert, toList) import Test.Hspec spec :: Spec spec = describe "W3C TraceContext TraceState" $ do describe "tracestateParser" $ do it "parses empty tracestate" $ do parseOnly tracestateParser "" `shouldBe` Right empty it "parses single key=value pair" $ do let result = parseOnly tracestateParser "vendor1=value1" result `shouldBe` Right (insert (Key "vendor1") (Value "value1") empty) it "parses multiple key=value pairs" $ do let result = parseOnly tracestateParser "vendor1=value1,vendor2=value2" case result of Right ts -> do let pairs = toList ts pairs `shouldContain` [(Key "vendor1", Value "value1")] pairs `shouldContain` [(Key "vendor2", Value "value2")] Left err -> expectationFailure $ "Parse failed: " ++ err it "parses key=value pairs with spaces" $ do let result = parseOnly tracestateParser " vendor1=value1 , vendor2=value2 " case result of Right ts -> do let pairs = toList ts pairs `shouldContain` [(Key "vendor1", Value "value1")] pairs `shouldContain` [(Key "vendor2", Value "value2")] Left err -> expectationFailure $ "Parse failed: " ++ err it "handles multi-tenant keys" $ do let result = parseOnly tracestateParser "tenant@vendor=value1" result `shouldBe` Right (insert (Key "tenant@vendor") (Value "value1") empty) it "handles special characters in keys" $ do let result = parseOnly tracestateParser "vendor-1_2*3/4@tenant=value1" result `shouldBe` Right (insert (Key "vendor-1_2*3/4@tenant") (Value "value1") empty) it "handles special characters in values" $ do let result = parseOnly tracestateParser "vendor1=value-with_special*chars/and@symbols" result `shouldBe` Right (insert (Key "vendor1") (Value "value-with_special*chars/and@symbols") empty) it "limits to 32 entries" $ do let pairs = [C8.pack $ "key" ++ show i ++ "=value" ++ show i | i <- [1 .. 40]] input = C8.intercalate "," pairs result = parseOnly tracestateParser input case result of Right ts -> length (toList ts) `shouldBe` 32 Left err -> expectationFailure $ "Parse failed: " ++ err it "rejects invalid key starting with uppercase" $ do let result = parseOnly tracestateParser "VENDOR=value1" result `shouldSatisfy` isLeft it "rejects invalid key with invalid characters" $ do let result = parseOnly tracestateParser "vendor$=value1" result `shouldSatisfy` isLeft it "rejects keys that are too long" $ do let longKey = T.replicate 257 "a" input = C8.pack $ T.unpack longKey ++ "=value1" result = parseOnly tracestateParser input result `shouldSatisfy` isLeft it "rejects values that are too long" $ do let longValue = T.replicate 257 "a" input = C8.pack $ "vendor1=" ++ T.unpack longValue result = parseOnly tracestateParser input result `shouldSatisfy` isLeft it "rejects invalid value with comma" $ do let result = parseOnly tracestateParser "vendor1=value,with,comma" result `shouldSatisfy` isLeft it "rejects invalid value with equals" $ do let result = parseOnly tracestateParser "vendor1=value=with=equals" result `shouldSatisfy` isLeft describe "encodeTraceState" $ do it "encodes empty tracestate" $ do encodeTraceState empty `shouldBe` "" it "encodes single key=value pair" $ do let ts = insert (Key "vendor1") (Value "value1") empty encodeTraceState ts `shouldBe` "vendor1=value1" it "encodes multiple key=value pairs" $ do let ts = fromList [(Key "vendor1", Value "value1"), (Key "vendor2", Value "value2")] let encoded = encodeTraceState ts -- Order might vary, so check both possibilities encoded `shouldSatisfy` \s -> s == "vendor1=value1,vendor2=value2" || s == "vendor2=value2,vendor1=value1" it "limits to 32 entries when encoding" $ do let buildTS n acc | n > 40 = acc | otherwise = buildTS (n + 1) $ insert (Key $ T.pack $ "key" ++ show n) (Value $ T.pack $ "value" ++ show n) acc ts = buildTS 1 empty encoded = encodeTraceState ts entryCount = length $ filter (== ',') $ C8.unpack encoded entryCount `shouldBe` 31 -- 32 entries = 31 commas it "handles special characters in encoding" $ do let ts = insert (Key "vendor-1_2*3/4@tenant") (Value "value-with_special*chars/and@symbols") empty encodeTraceState ts `shouldBe` "vendor-1_2*3/4@tenant=value-with_special*chars/and@symbols" describe "encodeTraceStateFull" $ do it "encodes empty tracestate" $ do encodeTraceStateFull empty `shouldBe` "" it "encodes single key=value pair" $ do let ts = insert (Key "vendor1") (Value "value1") empty encodeTraceStateFull ts `shouldBe` "vendor1=value1" it "preserves all entries beyond 32 limit" $ do let pairs = [(Key $ T.pack $ "key" ++ show i, Value $ T.pack $ "value" ++ show i) | i <- [1 .. 40]] ts = fromList pairs encoded = encodeTraceStateFull ts entryCount = length $ filter (== '=') $ C8.unpack encoded entryCount `shouldBe` 40 -- Should preserve all 40 entries it "does not filter oversized entries" $ do let longValue = T.replicate 200 "x" -- Much longer than 128 char limit ts = insert (Key "longentry") (Value longValue) empty encoded = encodeTraceStateFull ts encoded `shouldSatisfy` C8.isInfixOf "longentry=" C8.length encoded `shouldSatisfy` (> 200) it "matches encodeTraceState for small tracestates" $ do let ts = fromList [(Key "vendor1", Value "value1"), (Key "vendor2", Value "value2")] encodeTraceStateFull ts `shouldBe` encodeTraceState ts describe "round-trip property" $ do it "round-trips simple valid tracestate" $ do let validPairs = [("vendor1", "value1"), ("vendor2", "value2")] ts = fromList [(Key $ T.pack k, Value $ T.pack v) | (k, v) <- validPairs] encoded = encodeTraceState ts parsed = parseOnly tracestateParser encoded case parsed of Right ts' -> toList ts' `shouldBe` toList ts Left err -> expectationFailure $ "Round-trip failed: " ++ err it "round-trips complex valid tracestate" $ do let validPairs = [("tenant@vendor", "complex-value_with*chars"), ("simple123", "value")] ts = fromList [(Key $ T.pack k, Value $ T.pack v) | (k, v) <- validPairs] encoded = encodeTraceState ts parsed = parseOnly tracestateParser encoded case parsed of Right ts' -> toList ts' `shouldBe` toList ts Left err -> expectationFailure $ "Round-trip failed: " ++ err