packages feed

launchdarkly-server-sdk-4.0.0: test/Spec/Segment.hs

module Spec.Segment (allTests) where

import Data.Aeson.Types (Value (..))
import Data.Function ((&))
import Data.Generics.Product (getField)
import qualified Data.HashSet as HS
import Test.HUnit
import Util.Features

import LaunchDarkly.Server.Client
import LaunchDarkly.Server.Config
import LaunchDarkly.Server.Context (makeContext, withAttribute)
import LaunchDarkly.Server.Evaluate
import LaunchDarkly.Server.Features
import LaunchDarkly.Server.Operators
import LaunchDarkly.Server.Reference (makeLiteral)
import LaunchDarkly.Server.Store.Internal

makeEmptyStore = do
    handle <- makeStoreIO Nothing 0
    initializeStore handle mempty mempty
    pure handle

makeTestClient :: IO Client
makeTestClient = do
    client <- makeClient $ (makeConfig "") & configSetOffline True
    initializeStore (getField @"store" client) mempty mempty
    pure client

testExplicitIncludeUser :: Test
testExplicitIncludeUser = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right True)
    segmentContainsContext store segment org HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.fromList ["foo"]
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"
    org = makeContext "foo" "org"

testExplicitIncludeContextKind :: Test
testExplicitIncludeContextKind = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right True)
    segmentContainsContext store segment org HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = [SegmentTarget {values = HS.fromList ["foo"], contextKind = "user"}]
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"
    org = makeContext "foo" "org"

testExplicitExcludeUser :: Test
testExplicitExcludeUser = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.fromList ["foo"]
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"

testExplicitExcludeContextKind :: Test
testExplicitExcludeContextKind = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = [SegmentTarget {values = HS.fromList ["foo"], contextKind = "user"}]
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"

testExplicitIncludeHasPrecedence :: Test
testExplicitIncludeHasPrecedence = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right True)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.fromList ["foo"]
            , includedContexts = mempty
            , excluded = HS.fromList ["foo"]
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"

testExplicitIncludeContextsHasPrecedence :: Test
testExplicitIncludeContextsHasPrecedence = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right True)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = [SegmentTarget {values = HS.fromList ["foo"], contextKind = "user"}]
            , excluded = HS.fromList ["foo"]
            , excludedContexts = [SegmentTarget {values = HS.fromList ["foo"], contextKind = "user"}]
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"

testNeitherIncludedNorExcluded :: Test
testNeitherIncludedNorExcluded = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment user HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.fromList [""]
            , includedContexts = mempty
            , excluded = HS.fromList [""]
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules = []
            , version = 1
            , deleted = False
            }

    user = makeContext "foo" "user"

testMatchingRuleWithFullRollout :: Test
testMatchingRuleWithFullRollout = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment context HS.empty >>= assertEqual "" (Right True)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules =
                [ SegmentRule
                    { id = "rule"
                    , clauses =
                        [ Clause
                            { attribute = makeLiteral "email"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = [String "test@example.com"]
                            }
                        ]
                    , weight = Just 100000
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            , version = 1
            , deleted = False
            }

    context = makeContext "foo" "user" & withAttribute "email" "test@example.com"

testMatchingRuleWithZeroRollout :: Test
testMatchingRuleWithZeroRollout = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment context HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules =
                [ SegmentRule
                    { id = "rule"
                    , clauses =
                        [ Clause
                            { attribute = makeLiteral "email"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = [String "test@example.com"]
                            }
                        ]
                    , weight = Just 0
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            , version = 1
            , deleted = False
            }

    context = makeContext "foo" "user" & withAttribute "email" "test@example.com"

testMatchingRuleWithMultipleClauses :: Test
testMatchingRuleWithMultipleClauses = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment context HS.empty >>= assertEqual "" (Right True)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules =
                [ SegmentRule
                    { id = "rule"
                    , clauses =
                        [ Clause
                            { attribute = makeLiteral "email"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = [String "test@example.com"]
                            }
                        , Clause
                            { attribute = makeLiteral "name"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = ["bob"]
                            }
                        ]
                    , weight = Nothing
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            , version = 1
            , deleted = False
            }

    context =
        makeContext "foo" "user"
            & withAttribute "email" "test@example.com"
            & withAttribute "name" "bob"

testNonMatchingRuleWithMultipleClauses :: Test
testNonMatchingRuleWithMultipleClauses = TestCase $ do
    store <- makeEmptyStore
    segmentContainsContext store segment context HS.empty >>= assertEqual "" (Right False)
  where
    segment =
        Segment
            { key = "test"
            , included = HS.empty
            , includedContexts = mempty
            , excluded = HS.empty
            , excludedContexts = mempty
            , salt = "abcdef"
            , rules =
                [ SegmentRule
                    { id = "rule"
                    , clauses =
                        [ Clause
                            { attribute = makeLiteral "email"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = [String "test@example.com"]
                            }
                        , Clause
                            { attribute = makeLiteral "name"
                            , contextKind = "user"
                            , negate = False
                            , op = OpIn
                            , values = ["bill"]
                            }
                        ]
                    , weight = Nothing
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            , version = 1
            , deleted = False
            }

    context =
        makeContext "foo" "user"
            & withAttribute "email" "test@example.com"
            & withAttribute "name" "bob"

testCanDetectRecursiveSegments :: Test
testCanDetectRecursiveSegments = TestCase $ do
    client <- makeTestClient
    insertFlag (getField @"store" client) flag >>= (pure () @=?)
    insertSegment (getField @"store" client) segmentA >>= (pure () @=?)
    insertSegment (getField @"store" client) segmentB >>= (pure () @=?)
    boolVariationDetail client "a" (makeContext "b" "user") False >>= (expected @=?)
  where
    flag =
        (makeTestFlag "a" 1)
            { on = True
            , rules =
                [ Rule
                    { clauses =
                        [ Clause
                            { attribute = makeLiteral "key"
                            , contextKind = "user"
                            , op = OpSegmentMatch
                            , values = [String "segmentA"]
                            , negate = False
                            }
                        ]
                    , variationOrRollout =
                        VariationOrRollout
                            { variation = Just 0
                            , rollout = Nothing
                            }
                    , id = "rule-1"
                    , trackEvents = False
                    }
                ]
            , offVariation = Nothing
            }
    segmentA =
        Segment
            { key = "segmentA"
            , version = 1
            , deleted = False
            , included = mempty
            , includedContexts = mempty
            , excluded = mempty
            , excludedContexts = mempty
            , salt = ""
            , rules =
                [ SegmentRule
                    { clauses =
                        [ Clause
                            { attribute = makeLiteral "rule-1"
                            , contextKind = "user"
                            , op = OpSegmentMatch
                            , values = [String "segmentB"]
                            , negate = False
                            }
                        ]
                    , id = "rule-1"
                    , weight = Nothing
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            }
    segmentB =
        Segment
            { key = "segmentB"
            , version = 1
            , deleted = False
            , included = mempty
            , includedContexts = mempty
            , excluded = mempty
            , excludedContexts = mempty
            , salt = ""
            , rules =
                [ SegmentRule
                    { clauses =
                        [ Clause
                            { attribute = makeLiteral "rule-1"
                            , contextKind = "user"
                            , op = OpSegmentMatch
                            , values = [String "segmentA"]
                            , negate = False
                            }
                        ]
                    , id = "rule-1"
                    , weight = Nothing
                    , bucketBy = Nothing
                    , rolloutContextKind = Just "user"
                    }
                ]
            }
    expected =
        EvaluationDetail
            { value = False
            , variationIndex = Nothing
            , reason = EvaluationReasonError {errorKind = EvalErrorKindMalformedFlag}
            }

allTests :: Test
allTests =
    TestList
        [ testExplicitIncludeUser
        , testExplicitIncludeContextKind
        , testExplicitExcludeUser
        , testExplicitExcludeContextKind
        , testExplicitIncludeHasPrecedence
        , testExplicitIncludeContextsHasPrecedence
        , testNeitherIncludedNorExcluded
        , testMatchingRuleWithFullRollout
        , testMatchingRuleWithZeroRollout
        , testMatchingRuleWithMultipleClauses
        , testNonMatchingRuleWithMultipleClauses
        , testCanDetectRecursiveSegments
        ]