packages feed

effectful-tracing-0.1.0.0: test/Effectful/Tracing/IdsSpec.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      : Effectful.Tracing.IdsSpec
-- Description : Edge-case tests for the identifier hex codec and validity checks.
--
-- The round-trip property lives in "Effectful.Tracing.PropertySpec"; this file
-- pins the rejection and acceptance boundaries that round-trips never reach:
-- odd-length and non-hex input, wrong-length byte arrays, uppercase hex, the
-- all-zero sentinel, and lowercase rendering.
module Effectful.Tracing.IdsSpec
  ( tests
  ) where

import Data.ByteString qualified as BS
import Data.Maybe (isJust, isNothing)
import Data.Text (Text)
import Data.Text qualified as T

import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertBool, testCase, (@?=))

import Effectful.Tracing.Internal.Ids
  ( SpanId (SpanId)
  , TraceId (TraceId)
  , isValidSpanId
  , isValidTraceId
  , spanIdFromBytes
  , spanIdFromHex
  , traceIdFromBytes
  , traceIdFromHex
  , traceIdToHex
  )

tests :: TestTree
tests =
  testGroup
    "Identifiers"
    [ testGroup "hex parsing" hexTests
    , testGroup "byte construction" byteTests
    , testGroup "validity" validityTests
    ]

hexTests :: [TestTree]
hexTests =
  [ testCase "an odd-length string is rejected" $
      assertBool "odd length" (isNothing (traceIdFromHex (hexDigits 31)))
  , testCase "a too-short even-length string is rejected" $
      -- 30 hex chars decode to 15 bytes, one short of a trace id.
      assertBool "15 bytes" (isNothing (traceIdFromHex (hexDigits 30)))
  , testCase "a non-hex character is rejected" $
      assertBool "non-hex" (isNothing (traceIdFromHex "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"))
  , testCase "uppercase hex is accepted and renders back lowercase" $ do
      let upper = "4BF92F3577B34DA6A3CE929D0E0E4736"
          lower = "4bf92f3577b34da6a3ce929d0e0e4736"
      traceIdFromHex upper @?= traceIdFromHex lower
      fmap traceIdToHex (traceIdFromHex upper) @?= Just lower
  , testCase "a span id parses from exactly 16 hex characters" $
      assertBool "valid span hex" (isJust (spanIdFromHex "00f067aa0ba902b7"))
  , testCase "an odd-length span id string is rejected" $
      assertBool "odd span length" (isNothing (spanIdFromHex "00f067aa0ba902b"))
  ]

byteTests :: [TestTree]
byteTests =
  [ testCase "16 bytes makes a trace id" $
      assertBool "16 bytes" (isJust (traceIdFromBytes (BS.replicate 16 1)))
  , testCase "15 or 17 bytes is rejected as a trace id" $ do
      assertBool "15 bytes" (isNothing (traceIdFromBytes (BS.replicate 15 1)))
      assertBool "17 bytes" (isNothing (traceIdFromBytes (BS.replicate 17 1)))
  , testCase "8 bytes makes a span id" $
      assertBool "8 bytes" (isJust (spanIdFromBytes (BS.replicate 8 1)))
  , testCase "7 or 9 bytes is rejected as a span id" $ do
      assertBool "7 bytes" (isNothing (spanIdFromBytes (BS.replicate 7 1)))
      assertBool "9 bytes" (isNothing (spanIdFromBytes (BS.replicate 9 1)))
  ]

validityTests :: [TestTree]
validityTests =
  [ testCase "a correctly-sized but all-zero trace id is invalid" $
      isValidTraceId (TraceId (BS.replicate 16 0)) @?= False
  , testCase "a non-zero, correctly-sized trace id is valid" $
      isValidTraceId (TraceId (BS.replicate 16 1)) @?= True
  , testCase "a wrong-length trace id is invalid" $
      isValidTraceId (TraceId (BS.replicate 15 1)) @?= False
  , testCase "a correctly-sized but all-zero span id is invalid" $
      isValidSpanId (SpanId (BS.replicate 8 0)) @?= False
  , testCase "a non-zero, correctly-sized span id is valid" $
      isValidSpanId (SpanId (BS.replicate 8 1)) @?= True
  , testCase "a wrong-length span id is invalid" $
      isValidSpanId (SpanId (BS.replicate 7 1)) @?= False
  ]

-- | A string of @n@ copies of the hex digit @a@.
hexDigits :: Int -> Text
hexDigits n = T.replicate n "a"