packages feed

jsonlogic-0.1.0.0: test/Operation/Data/TestMissingSome.hs

module Operation.Data.TestMissingSome (missingSomeUnitTests, missingSomeGeneratorTests) where

import Generator.Data
import Generator.Generic (genGenericJsonNumber)
import Generator.Utils
import Hedgehog (Gen, forAll, property, (===))
import qualified Hedgehog as H (assert, failure)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import JsonLogic.Json
import JsonLogic.Pure.Evaluator
import Test.Tasty
import Test.Tasty.HUnit as U
import Utils

missingSomeUnitTests :: TestTree
missingSomeUnitTests =
  testGroup
    "missing_some unit tests"
    [ testCase "logic {\"missing_some\":[1, []]} data {} => []" $
        U.assertEqual
          "empty case"
          (Right $ jArr [])
          (apply [] (jObj [("missing_some", jArr [jNum 0, jArr []])]) (jObj [])),
      testCase "logic {\"missing_some\":[1, [\"a\", \"b\", \"c\"]]} data {\"a\":\"apple\"} => [\"b\",\"c\"]" $
        U.assertEqual
          "first test case on site"
          (Right $ jArr [])
          (apply [] (jObj [("missing_some", jArr [jNum 1, jArr [jStr "a", jStr "b", jStr "c"]])]) (jObj [("a", jStr "apple")])),
      testCase "logic {\"missing_some\":[2, [\"a\", \"b\", \"c\"]]} data {\"a\":\"apple\"} => [\"b\",\"c\"]" $
        U.assertEqual
          "second test case on site"
          (Right $ jArr [jStr "b", jStr "c"])
          (apply [] (jObj [("missing_some", jArr [jNum 2, jArr [jStr "a", jStr "b", jStr "c"]])]) (jObj [("a", jStr "apple")])),
      testCase
        ( "logic {\"if\" :[{\"merge\": [{\"missing\":[\"first_name\", \"last_name\"]},{\"missing_some\":[1, [\"cell_phone\", \"home_phone\"] ]}]}, \"We require first name, last name, and one phone number.\",\"OK to proceed\"]}"
            ++ "data {\"first_name\":\"Bruce\", \"last_name\":\"Wayne\"} => \"We require first name, last name, and one phone number.\""
        )
        $ U.assertEqual
          "third test case on site"
          (Right $ jStr "We require first name, last name, and one phone number.")
          (apply [] (jObj [("if", jArr [jObj [("merge", jArr [jObj [("missing", jArr [jStr "first_name", jStr "last_name"])], jObj [("missing_some", jArr [jNum 1, jArr [jStr "cell_phone", jStr "home_phone"]])]])], jStr "We require first name, last name, and one phone number.", jStr "OK to proceed"])]) (jObj [("first_name", jStr "Bruce"), ("last_name", jStr "Wayne")]))
    ]

missingSomeGeneratorTests :: TestTree
missingSomeGeneratorTests =
  testGroup
    "missing_some generator tests"
    [ hTestProperty "missing_some over empty" $
        property $ do
          -- Missing some over random integer
          (jsonNumber, _) <- forAll genGenericJsonNumber
          let missingSomeEmpty = jObj [("missing_some", jArr [jsonNumber, jArr []])]
          -- Random Json data
          dataJsonArray <- forAll $ Gen.sized genSizedRandomJsonArray
          dataJsonObject <- forAll $ Gen.sized genSizedRandomJsonObject
          -- Indexing empty missing_some over random data returns empty array
          Right (jArr []) === apply [] missingSomeEmpty dataJsonArray
          Right (jArr []) === apply [] missingSomeEmpty (JsonObject dataJsonObject),
      hTestProperty "missing_some when all keys are present" $
        property $ do
          -- Generate flat array as data
          jsonData@(JsonArray js) <- forAll $ increaseSizeBy 1 $ Gen.sized genSizedFlatArray
          -- Generate a random list of indexes
          indexes <- forAll $ genRandomJsonIndexes (0, length js - 1)
          -- Missing a random integer when all are present will result in an empty array
          x <- forAll $ Gen.int $ Range.constant 0 30
          let rule = jObj [("missing_some", jArr [jNum $ fromIntegral x, jArr indexes])]
          Right (jArr []) === apply [] rule jsonData,
      hTestProperty "missing_some when no keys are present" $
        property $ do
          -- Generate a flat array as data
          jsonData@(JsonArray js) <- forAll $ increaseSizeBy 1 $ Gen.sized genSizedFlatArray
          -- Generates random list of indexes
          missingIndexes <- forAll $ genRandomJsonIndexes (length js, length js * 2)
          -- Construct rule
          nrMissing <- forAll $ Gen.int $ Range.constant 1 30
          let rule = jObj [("missing_some", jArr [jNum $ fromIntegral nrMissing, JsonArray missingIndexes])]
          -- Always should return entire list of missing items
          Right (jArr missingIndexes) === apply [] rule jsonData,
      hTestProperty "missing_some when some keys are present" $
        property $ do
          -- Generate a flat array as data
          jsonData@(JsonArray js) <- forAll $ increaseSizeBy 1 $ Gen.sized genSizedFlatArray
          -- On average half of the indexes are missing and half of the indexes are present
          presentIndexes <- forAll $ genRandomJsonIndexes (0, length js - 1)
          missingIndexes <- forAll $ genRandomJsonIndexes (length js, length js * 2)
          -- The amount of items required is put in a rule
          nrRequired <- forAll $ fromIntegral <$> (Gen.int $ Range.constant 0 (length js * 2) :: Gen Int)
          let rule = jObj [("missing_some", jArr [jNum nrRequired, JsonArray $ presentIndexes ++ missingIndexes])]
          -- Evaluate the rule with the data
          case apply [] rule jsonData of
            -- If there are more indexes present than the number required then it returns an empty array.
            Right (JsonArray []) -> H.assert $ fromIntegral (length presentIndexes) >= nrRequired || null missingIndexes
            -- Otherwise it returns all the missing items
            res@(Right (JsonArray _)) -> Right (jArr missingIndexes) === res
            -- Should never return any other type
            _ -> H.failure
    ]

-- | Given a range, generate a random Json list with these indexes as numbers
genRandomJsonIndexes :: (Int, Int) -> Gen [Json]
genRandomJsonIndexes (lowerBound, upperBound) = map (JsonNumber . fromIntegral) <$> Gen.subsequence [lowerBound .. upperBound]