packages feed

sajson-0.1.0.0: test/Spec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Main where

import Data.Aeson as A
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.HashMap.Strict as HM
import Data.Sajson as S
import Data.Scientific
import Data.String
import qualified Data.Text as T
import qualified Data.Vector as V
import Test.Hspec
import Test.QuickCheck

newtype AcceptableValue = AcceptableValue { getAcceptableValue :: Value } deriving (Show, Eq)

instance Arbitrary AcceptableValue where
  arbitrary = AcceptableValue <$> oneof [Array <$> genArray, Object <$> genObject]
      where genText = T.pack <$> listOf (oneof [choose (' ', '~'), choose ('\256', '\383'), choose ('\19968', '\40959'), choose ('\128512', '\128591')]) -- latin text, CJK ideographs, and emoji
            genNumber = fromIntegral <$> choose (-9007199254740992, 9007199254740992 :: Int)
            genArray = V.fromList <$> reducedSize (listOf genValue)
            genObject = HM.fromList <$> reducedSize (listOf ((,) <$> genText <*> genValue))
            genValue = oneof [Number <$> genNumber, pure Null, Bool <$> arbitrary, String <$> genText, Array <$> genArray, Object <$> genObject]
            reducedSize = scale (`div` 2)

main :: IO ()
main = hspec $ do
  describe "sajsonParse" $ do
    it "returns proper error for truncated JSON document" $ do
      r <- sajsonParse "{\"abc\":123,\"def\":[1,2,3"
      r `shouldBe` Left SajsonParseError {sajsonParseErrorLine = 1, sajsonParseErrorColumn = 24, sajsonParseErrorMessage = "unexpected end of input"}
    describe "returns Right for correct JSON document" $ do
      it "object of null" $ do
        r <- sajsonParse "{\"abc\":null}"
        r `shouldBe` Right (object ["abc" .= Null])
      it "object of true" $ do
        r <- sajsonParse "{\"abc\":true}"
        r `shouldBe` Right (object ["abc" .= Bool True])
      it "object of false" $ do
        r <- sajsonParse "{\"abc\":false}"
        r `shouldBe` Right (object ["abc" .= Bool False])
      it "object of integer" $ do
        r <- sajsonParse "{\"abc\":2147483647}"
        r `shouldBe` Right (object ["abc" .= Number 2147483647])
      it "object of double" $ do
        r <- sajsonParse "{\"abc\":3.141592653589793}"
        r `shouldBe` Right (object ["abc" .= Number 3.141592653589793])
      it "object of big integer (exponential notation)" $ do
        r <- sajsonParse "{\"abc\":1e308}"
        r `shouldBe` Right (object ["abc" .= Number (scientific 1 308)])
      it "object of array of number" $ do
        r <- sajsonParse "{\"abc\":[1,2,3],\"def\":[0.1,0.2]}"
        r `shouldBe` Right (object ["abc" .= [1.0 :: Scientific, 2.0, 3.0], "def" .= [0.1 :: Scientific, 0.2]])
    it "round trips acceptable values with aeson" $ property $ \(AcceptableValue v) -> ioProperty $ do
      let encoded = encode v
      decoded <- sajsonParse (BL.toStrict encoded)
      decoded `shouldBe` Right v
      pure True

  let testEither :: IsString s => (forall a. FromJSON a => s -> Either String a) -> (forall a. FromJSON a => s -> Either String a) -> String -> Spec
      testEither f f' n = describe n $ do
        it "handles incorrect JSON" $
          f "[0" `shouldBe` (Left "Error in sajson parser: line 1 column 3: unexpected end of input" :: Either String [Int])
        it "handles correct JSON but incorrect schema" $ do
          let s = "{\"a\":42}"
          f s `shouldBe` (f' s :: Either String [Int])

      testMaybe :: IsString s => (forall a. FromJSON a => s -> Maybe a) -> String -> Spec
      testMaybe f n = describe n $ do
        it "handles incorrect JSON" $
          f "[0" `shouldBe` (Nothing :: Maybe [Int])
        it "handles correct JSON but incorrect schema" $ do
          let s = "{\"a\":42}"
          f s `shouldBe` (Nothing :: Maybe [Int])

  testEither S.eitherDecodeStrict A.eitherDecodeStrict "eitherDecodeStrict"
  testEither S.eitherDecode A.eitherDecode "eitherDecode"

  testMaybe S.decodeStrict "decodeStrict"
  testMaybe S.decode "decode"