packages feed

link-canonical-0.1.0.0: test/Link/Canonical/TrackingSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Link.Canonical.TrackingSpec (tests) where

import Data.Either (fromRight)
import Data.Set qualified as Set
import Data.Text (Text)
import Link.Canonical
import Test.Tasty
import Test.Tasty.HUnit
import Text.URI qualified as URI

tests :: TestTree
tests =
  testGroup
    "Tracking"
    [ testCase "strips utm_source parameter" $ do
        let uri = unsafeParseURI "https://example.com/?utm_source=twitter&id=123"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        keys @?= ["id"],
      testCase "strips utm_* parameters" $ do
        let uri = unsafeParseURI "https://example.com/?utm_source=x&utm_medium=y&utm_campaign=z&id=1"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        keys @?= ["id"],
      testCase "strips fbclid parameter" $ do
        let uri = unsafeParseURI "https://example.com/?fbclid=abc123&page=1"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        keys @?= ["page"],
      testCase "strips gclid parameter" $ do
        let uri = unsafeParseURI "https://example.com/?gclid=xyz&page=1"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        keys @?= ["page"],
      testCase "respects allowlist" $ do
        let config =
              defaultConfig
                { tracking =
                    TrackingConfig
                      { denyPatterns = ["ref"],
                        allowlist = Set.fromList ["ref"]
                      }
                }
            uri = unsafeParseURI "https://example.com/?ref=affiliate&id=1"
            result = normalizeUri config [] uri
            keys = getParamKeys result
        -- ref should be kept because it's in the allowlist
        keys @?= ["id", "ref"],
      testCase "pattern matching with wildcard" $ do
        let uri = unsafeParseURI "https://example.com/?mc_cid=abc&mc_eid=def&id=1"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        -- mc_* should match both mc_cid and mc_eid
        keys @?= ["id"],
      testCase "preserves non-tracking parameters" $ do
        let uri = unsafeParseURI "https://example.com/?page=1&sort=desc&filter=active"
            result = normalizeUri defaultConfig [] uri
            keys = getParamKeys result
        keys @?= ["filter", "page", "sort"],
      testGroup
        "Additional tracking patterns"
        [ testCase "strips _ga and _gl (Google Analytics)" $ do
            let uri = unsafeParseURI "https://example.com/?_ga=1.2.3&_gl=abc&id=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["id"],
          testCase "strips msclkid (Microsoft Ads)" $ do
            let uri = unsafeParseURI "https://example.com/?msclkid=abc123&page=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["page"],
          testCase "strips dclid (DoubleClick)" $ do
            let uri = unsafeParseURI "https://example.com/?dclid=xyz789&id=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["id"],
          testCase "strips oly_* (Omeda)" $ do
            let uri = unsafeParseURI "https://example.com/?oly_anon_id=abc&oly_enc_id=def&id=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["id"],
          testCase "strips zanpid (Zanox)" $ do
            let uri = unsafeParseURI "https://example.com/?zanpid=123456&page=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["page"],
          testCase "strips igshid (Instagram)" $ do
            let uri = unsafeParseURI "https://example.com/?igshid=abc123&post=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["post"],
          testCase "strips si (Spotify)" $ do
            let uri = unsafeParseURI "https://example.com/?si=abc123def&track=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["track"],
          testCase "strips twclid (Twitter)" $ do
            let uri = unsafeParseURI "https://example.com/?twclid=abc&id=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["id"],
          testCase "strips hsa_* (HubSpot)" $ do
            let uri = unsafeParseURI "https://example.com/?hsa_cam=123&hsa_grp=456&id=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["id"],
          testCase "strips mkt_tok (Marketo)" $ do
            let uri = unsafeParseURI "https://example.com/?mkt_tok=abc123&page=1"
                result = normalizeUri defaultConfig [] uri
                keys = getParamKeys result
            keys @?= ["page"]
        ]
    ]

-- Helper functions

unsafeParseURI :: Text -> URI
unsafeParseURI t = fromRight (error $ "Failed to parse: " <> show t) $ URI.mkURI t

getParamKeys :: URI -> [Text]
getParamKeys uri = map getParamKey $ URI.uriQuery uri

getParamKey :: URI.QueryParam -> Text
getParamKey (URI.QueryFlag t) = URI.unRText t
getParamKey (URI.QueryParam k _) = URI.unRText k