{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-} -- FIXME
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module JSONTest where
import Control.Monad.IO.Class (MonadIO)
import Data.Aeson
import qualified Data.Vector as V (fromList)
import Test.HUnit (assertBool)
import Test.Hspec.Expectations ()
import Database.Persist
import Database.Persist.Postgresql.JSON
import PgInit
share [mkPersist persistSettings, mkMigrate "jsonTestMigrate"] [persistLowerCase|
TestValue
json Value
deriving Show
|]
cleanDB :: (BaseBackend backend ~ SqlBackend, PersistQueryWrite backend, MonadIO m) => ReaderT backend m ()
cleanDB = deleteWhere ([] :: [Filter TestValue])
emptyArr :: Value
emptyArr = toJSON ([] :: [Value])
insert' :: (MonadIO m, PersistStoreWrite backend, BaseBackend backend ~ SqlBackend)
=> Value -> ReaderT backend m (Key TestValue)
insert' = insert . TestValue
(=@=) :: MonadIO m => String -> Bool -> m ()
s =@= b = liftIO $ assertBool s b
matchKeys :: (Show record, Show (Key record), MonadIO m, Eq (Key record))
=> String -> [Key record] -> [Entity record] -> m ()
matchKeys s ys xs = do
msg1 =@= (xLen == yLen)
forM_ ys $ \y -> msg2 y =@= (y `elem` ks)
where ks = entityKey <$> xs
xLen = length xs
yLen = length ys
msg1 = mconcat
[ s, "\nexpected: ", show yLen
, "\n but got: ", show xLen
, "\n[xs: ", show xs
, ", ys: ", show ys, "]"
]
msg2 y = mconcat
[ s, ": "
, "key \"", show y
, "\" not in result:\n ", show ks
]
specs :: Spec
specs = describe "postgresql's JSON operators behave" $ do
it "migrate, clean table, insert values and check queries" $ asIO $ runConn $ do
runMigration jsonTestMigrate
cleanDB
liftIO $ putStrLn "\n- - - - - Inserting JSON values - - - - -\n"
nullK <- insert' Null
boolTK <- insert' $ Bool True
boolFK <- insert' $ toJSON False
num0K <- insert' $ Number 0
num1K <- insert' $ Number 1
numBigK <- insert' $ toJSON (1234567890 :: Int)
numFloatK <- insert' $ Number 0.0
numSmallK <- insert' $ Number 0.0000000000000000123
numFloat2K <- insert' $ Number 1.5
-- numBigFloatK will turn into 9876543210.123457 because JSON
numBigFloatK <- insert' $ toJSON (9876543210.123456789 :: Double)
strNullK <- insert' $ String ""
strObjK <- insert' $ String "{}"
strArrK <- insert' $ String "[]"
strAK <- insert' $ String "a"
strTestK <- insert' $ toJSON ("testing" :: Text)
str2K <- insert' $ String "2"
strFloatK <- insert' $ String "0.45876"
arrNullK <- insert' $ Array $ V.fromList []
arrListK <- insert' $ toJSON ([emptyArr,emptyArr,toJSON [emptyArr,emptyArr]])
arrList2K <- insert' $ toJSON [emptyArr,toJSON [Number 3,Bool False],toJSON [emptyArr,toJSON [Object mempty]]]
arrFilledK <- insert' $ toJSON [Null, Number 4, String "b", Object mempty, emptyArr, object [ "test" .= [Null], "test2" .= String "yes"]]
objNullK <- insert' $ Object mempty
objTestK <- insert' $ object ["test" .= Null, "test1" .= String "no"]
objDeepK <- insert' $ object ["c" .= Number 24.986, "foo" .= object ["deep1" .= Bool True]]
----------------------------------------------------------------------------------------
liftIO $ putStrLn "\n- - - - - Starting @> tests - - - - -\n"
-- An empty Object matches any object
selectList [TestValueJson @>. Object mempty] []
>>= matchKeys "1" [objNullK,objTestK,objDeepK]
-- {"test":null,"test1":"no"} @> {"test":null} == True
selectList [TestValueJson @>. object ["test" .= Null]] []
>>= matchKeys "2" [objTestK]
-- {"c":24.986,"foo":{"deep1":true"}} @> {"foo":{}} == True
selectList [TestValueJson @>. object ["foo" .= object []]] []
>>= matchKeys "3" [objDeepK]
-- {"c":24.986,"foo":{"deep1":true"}} @> {"foo":"nope"} == False
selectList [TestValueJson @>. object ["foo" .= String "nope"]] []
>>= matchKeys "4" []
-- {"c":24.986,"foo":{"deep1":true"}} @> {"foo":{"deep1":true}} == True
selectList [TestValueJson @>. (object ["foo" .= object ["deep1" .= True]])] []
>>= matchKeys "5" [objDeepK]
-- {"c":24.986,"foo":{"deep1":true"}} @> {"deep1":true} == False
selectList [TestValueJson @>. object ["deep1" .= True]] []
>>= matchKeys "6" []
-- An empty Array matches any array
selectList [TestValueJson @>. emptyArr] []
>>= matchKeys "7" [arrNullK,arrListK,arrList2K,arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [4] == True
selectList [TestValueJson @>. toJSON [4 :: Int]] []
>>= matchKeys "8" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [null,"b"] == True
selectList [TestValueJson @>. toJSON [Null, String "b"]] []
>>= matchKeys "9" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [null,"d"] == False
selectList [TestValueJson @>. toJSON [emptyArr, String "d"]] []
>>= matchKeys "10" []
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [[],"b",{"test":[null],"test2":"yes"},4,null,{}] == True
selectList [TestValueJson @>. toJSON [emptyArr, String "b", object [ "test" .= [Null], "test2" .= String "yes"], Number 4, Null, Object mempty]] []
>>= matchKeys "11" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == False
selectList [TestValueJson @>. toJSON [Null, Number 4, String "b", Object mempty, emptyArr, object [ "test" .= [Null], "test2" .= String "yes"], Bool False]] []
>>= matchKeys "12" []
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [{}] == True
selectList [TestValueJson @>. toJSON [Object mempty]] []
>>= matchKeys "13" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [{"test":[]}] == True
selectList [TestValueJson @>. toJSON [object ["test" .= emptyArr]]] []
>>= matchKeys "14" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [{"test1":[null]}] == False
selectList [TestValueJson @>. toJSON [object ["test1" .= [Null]]]] []
>>= matchKeys "15" []
-- [[],[],[[],[]]] @> [[]] == True
-- [[],[3,false],[[],[{}]]] @> [[]] == True
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> [[]] == True
selectList [TestValueJson @>. toJSON [emptyArr]] []
>>= matchKeys "16" [arrListK,arrList2K,arrFilledK]
-- [[],[3,false],[[],[{}]]] @> [[3]] == True
selectList [TestValueJson @>. toJSON [[3 :: Int]]] []
>>= matchKeys "17" [arrList2K]
-- [[],[3,false],[[],[{}]]] @> [[true,3]] == False
selectList [TestValueJson @>. toJSON [[Bool True, Number 3]]] []
>>= matchKeys "18" []
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> 4 == True
selectList [TestValueJson @>. Number 4] []
>>= matchKeys "19" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> 4 == True
selectList [TestValueJson @>. Number 99] []
>>= matchKeys "20" []
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> "b" == True
selectList [TestValueJson @>. String "b"] []
>>= matchKeys "21" [arrFilledK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> "{}" == False
selectList [TestValueJson @>. String "{}"] []
>>= matchKeys "22" [strObjK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] @> {"test":[null],"test2":"yes"} == False
selectList [TestValueJson @>. object [ "test" .= [Null], "test2" .= String "yes"]] []
>>= matchKeys "23" []
-- "testing" @> "testing" == True
selectList [TestValueJson @>. String "testing"] []
>>= matchKeys "24" [strTestK]
-- "testing" @> "Testing" == False
selectList [TestValueJson @>. String "Testing"] []
>>= matchKeys "25" []
-- "testing" @> "test" == False
selectList [TestValueJson @>. String "test"] []
>>= matchKeys "26" []
-- "testing" @> {"testing":1} == False
selectList [TestValueJson @>. object ["testing" .= Number 1]] []
>>= matchKeys "27" []
-- 1 @> 1 == True
selectList [TestValueJson @>. toJSON (1 :: Int)] []
>>= matchKeys "28" [num1K]
-- 0 @> 0.0 == True
-- 0.0 @> 0.0 == True
selectList [TestValueJson @>. toJSON (0.0 :: Double)] []
>>= matchKeys "29" [num0K,numFloatK]
-- 1234567890 @> 123456789 == False
selectList [TestValueJson @>. toJSON (123456789 :: Int)] []
>>= matchKeys "30" []
-- 1234567890 @> 234567890 == False
selectList [TestValueJson @>. toJSON (234567890 :: Int)] []
>>= matchKeys "31" []
-- 1 @> "1" == False
selectList [TestValueJson @>. String "1"] []
>>= matchKeys "32" []
-- 1234567890 @> [1,2,3,4,5,6,7,8,9,0] == False
selectList [TestValueJson @>. toJSON ([1,2,3,4,5,6,7,8,9,0] :: [Int])] []
>>= matchKeys "33" []
-- true @> true == True
-- false @> true == False
selectList [TestValueJson @>. toJSON True] []
>>= matchKeys "34" [boolTK]
-- false @> false == True
-- true @> false == False
selectList [TestValueJson @>. Bool False] []
>>= matchKeys "35" [boolFK]
-- true @> "true" == False
selectList [TestValueJson @>. String "true"] []
>>= matchKeys "36" []
-- null @> null == True
selectList [TestValueJson @>. Null] []
>>= matchKeys "37" [nullK,arrFilledK]
-- null @> "null" == False
selectList [TestValueJson @>. String "null"] []
>>= matchKeys "38" []
----------------------------------------------------------------------------------------
liftIO $ putStrLn "\n- - - - - Starting <@ tests - - - - -\n"
-- {} <@ {"test":null,"test1":"no","blabla":[]} == True
-- {"test":null,"test1":"no"} <@ {"test":null,"test1":"no","blabla":[]} == True
selectList [TestValueJson <@. object ["test" .= Null, "test1" .= String "no", "blabla" .= emptyArr]] []
>>= matchKeys "39" [objNullK,objTestK]
-- [] <@ [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True
-- null <@ [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True
-- false <@ [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] <@ [null,4,"b",{},[],{"test":[null],"test2":"yes"},false] == True
selectList [TestValueJson <@. toJSON [Null, Number 4, String "b", Object mempty, emptyArr, object [ "test" .= [Null], "test2" .= String "yes"], Bool False]] []
>>= matchKeys "40" [arrNullK,arrFilledK,boolFK,nullK]
-- "a" <@ "a" == True
selectList [TestValueJson <@. String "a"] []
>>= matchKeys "41" [strAK]
-- 9876543210.123457 <@ 9876543210.123457 == False
selectList [TestValueJson <@. Number 9876543210.123457] []
>>= matchKeys "42" [numBigFloatK]
-- 9876543210.123457 <@ 9876543210.123456789 == False
selectList [TestValueJson <@. Number 9876543210.123456789] []
>>= matchKeys "43" []
-- null <@ null == True
selectList [TestValueJson <@. Null] []
>>= matchKeys "44" [nullK]
----------------------------------------------------------------------------------------
liftIO $ putStrLn "\n- - - - - Starting ? tests - - - - -\n"
arrList3K <- insert' $ toJSON [toJSON [String "a"], Number 1]
arrList4K <- insert' $ toJSON [String "a", String "b", String "c", String "d"]
objEmptyK <- insert' $ object ["" .= Number 9001]
objFullK <- insert' $ object ["a" .= Number 1, "b" .= Number 2, "c" .= Number 3, "d" .= Number 4]
-- {"test":null,"test1":"no"} ? "test" == True
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ? "test" == False
selectList [TestValueJson ?. "test"] []
>>= matchKeys "45" [objTestK]
-- {"c":24.986,"foo":{"deep1":true"}} ? "deep1" == False
selectList [TestValueJson ?. "deep1"] []
>>= matchKeys "46" []
-- "{}" ? "{}" == True
-- {} ? "{}" == False
selectList [TestValueJson ?. "{}"] []
>>= matchKeys "47" [strObjK]
-- {} ? "" == False
-- "" ? "" == True
-- {"":9001} ? "" == True
selectList [TestValueJson ?. ""] []
>>= matchKeys "48" [strNullK,objEmptyK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ? "b" == True
selectList [TestValueJson ?. "b"] []
>>= matchKeys "49" [arrFilledK,arrList4K,objFullK]
-- [["a"]] ? "a" == False
-- "a" ? "a" == True
-- ["a","b","c","d"] ? "a" == True
-- {"a":1,"b":2,"c":3,"d":4} ? "a" == True
selectList [TestValueJson ?. "a"] []
>>= matchKeys "50" [strAK,arrList4K,objFullK]
-- "[]" ? "[]" == True
-- [] ? "[]" == False
selectList [TestValueJson ?. "[]"] []
>>= matchKeys "51" [strArrK]
-- null ? "null" == False
selectList [TestValueJson ?. "null"] []
>>= matchKeys "52" []
-- true ? "true" == False
selectList [TestValueJson ?. "true"] []
>>= matchKeys "53" []
----------------------------------------------------------------------------------------
liftIO $ putStrLn "\n- - - - - Starting ?| tests - - - - -\n"
-- "a" ?| ["a","b","c"] == True
-- [["a"],1] ?| ["a","b","c"] == False
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?| ["a","b","c"] == True
-- ["a","b","c","d"] ?| ["a","b","c"] == True
-- {"a":1,"b":2,"c":3,"d":4} ?| ["a","b","c"] == True
selectList [TestValueJson ?|. ["a","b","c"]] []
>>= matchKeys "54" [strAK,arrFilledK,objDeepK,arrList4K,objFullK]
-- "{}" ?| ["{}"] == True
-- {} ?| ["{}"] == False
selectList [TestValueJson ?|. ["{}"]] []
>>= matchKeys "55" [strObjK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?| ["test"] == False
-- "testing" ?| ["test"] == False
-- {"test":null,"test1":"no"} ?| ["test"] == True
selectList [TestValueJson ?|. ["test"]] []
>>= matchKeys "56" [objTestK]
-- {"c":24.986,"foo":{"deep1":true"}} ?| ["deep1"] == False
selectList [TestValueJson ?|. ["deep1"]] []
>>= matchKeys "57" []
-- ANYTHING ?| [] == False
selectList [TestValueJson ?|. []] []
>>= matchKeys "58" []
-- true ?| ["true","null","1"] == False
-- null ?| ["true","null","1"] == False
-- 1 ?| ["true","null","1"] == False
selectList [TestValueJson ?|. ["true","null","1"]] []
>>= matchKeys "59" []
-- [] ?| ["[]"] == False
-- "[]" ?| ["[]"] == True
selectList [TestValueJson ?|. ["[]"]] []
>>= matchKeys "60" [strArrK]
----------------------------------------------------------------------------------------
liftIO $ putStrLn "\n- - - - - Starting ?& tests - - - - -\n"
-- ANYTHING ?& [] == True
selectList [TestValueJson ?&. []] []
>>= matchKeys "61" [ nullK
, boolTK, boolFK
, num0K, num1K, numBigK, numFloatK, numSmallK, numFloat2K, numBigFloatK
, strNullK, strObjK, strArrK, strAK, strTestK, str2K, strFloatK
, arrNullK, arrListK, arrList2K, arrFilledK
, objNullK, objTestK, objDeepK
, arrList3K, arrList4K
, objEmptyK, objFullK
]
-- "a" ?& ["a"] == True
-- [["a"],1] ?& ["a"] == False
-- ["a","b","c","d"] ?& ["a"] == True
-- {"a":1,"b":2,"c":3,"d":4} ?& ["a"] == True
selectList [TestValueJson ?&. ["a"]] []
>>= matchKeys "62" [strAK,arrList4K,objFullK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?& ["b","c"] == False
-- {"c":24.986,"foo":{"deep1":true"}} ?& ["b","c"] == False
-- ["a","b","c","d"] ?& ["b","c"] == True
-- {"a":1,"b":2,"c":3,"d":4} ?& ["b","c"] == True
selectList [TestValueJson ?&. ["b","c"]] []
>>= matchKeys "63" [arrList4K,objFullK]
-- {} ?& ["{}"] == False
-- "{}" ?& ["{}"] == True
selectList [TestValueJson ?&. ["{}"]] []
>>= matchKeys "64" [strObjK]
-- [null,4,"b",{},[],{"test":[null],"test2":"yes"}] ?& ["test"] == False
-- "testing" ?& ["test"] == False
-- {"test":null,"test1":"no"} ?& ["test"] == True
selectList [TestValueJson ?&. ["test"]] []
>>= matchKeys "65" [objTestK]
-- {"c":24.986,"foo":{"deep1":true"}} ?& ["deep1"] == False
selectList [TestValueJson ?&. ["deep1"]] []
>>= matchKeys "66" []
-- "a" ?& ["a","e"] == False
-- ["a","b","c","d"] ?& ["a","e"] == False
-- {"a":1,"b":2,"c":3,"d":4} ?& ["a","e"] == False
selectList [TestValueJson ?&. ["a","e"]] []
>>= matchKeys "67" []
-- [] ?& ["[]"] == False
-- "[]" ?& ["[]"] == True
selectList [TestValueJson ?&. ["[]"]] []
>>= matchKeys "68" [strArrK]
-- THIS WILL FAIL IF THE IMPLEMENTATION USES
-- @ '{null}' @
-- INSTEAD OF
-- @ ARRAY['null'] @
-- null ?& ["null"] == False
selectList [TestValueJson ?&. ["null"]] []
>>= matchKeys "69" []
-- [["a"],1] ?& ["1"] == False
-- "1" ?& ["1"] == True
selectList [TestValueJson ?&. ["1"]] []
>>= matchKeys "70" []
-- {} ?& [""] == False
-- [] ?& [""] == False
-- "" ?& [""] == True
-- {"":9001} ?& [""] == True
selectList [TestValueJson ?&. [""]] []
>>= matchKeys "71" [strNullK,objEmptyK]