hq-json-0.1.1: test/HQ/JSON/ParserSpec.hs
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
module HQ.JSON.ParserSpec (spec) where
import Data.Aeson (Value (..))
import Data.Aeson.Key (fromText)
import Data.Aeson.KeyMap qualified as KeyMap
import HQ.JSON.Event (JSONEvent (..))
import HQ.JSON.Parser (parseValue, parseValueEvents)
import Relude hiding (Compose, id)
import Test.HQ (decodeChunks)
import Test.Syd
spec :: Spec
spec = describe "HQ.JSON.Parser" $ do
parseValueSpec
parseValueEventsSpec
--------------------------------------------------------------------------------
-- parseValue
--------------------------------------------------------------------------------
parseValueSpec :: Spec
parseValueSpec = describe "parseValue" $ do
it "parses values" $ forM_ valueCases $ \(input, expected) ->
parseValue input `shouldBe` Right expected
it "rejects invalid input" $ forM_ invalidCases $ \input ->
parseValue input `shouldSatisfy` isLeft
valueCases :: [(Text, Value)]
valueCases =
[ ("42", Number 42),
("-7", Number (-7)),
("0.5", Number 0.5),
("1e10", Number 1e10),
("-2.5e-3", Number (-2.5e-3)),
("\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"", String "\"\\/\b\f\n\r\t"),
("\"caf\\u00e9\"", String "café"),
("\"\\uD83D\\uDE00\"", String "\x1F600"),
("{\"k\\u0041\":1}", Object (KeyMap.fromList [(fromText "kA", Number 1)]))
]
invalidCases :: [Text]
invalidCases =
[ "one",
"\"\\x\"",
"\"\\uD83D\"",
"\"\\uDE00\"",
"\"\\uD83Dx\"",
"\"a\SOHb\""
]
--------------------------------------------------------------------------------
-- parseValueEvents
--------------------------------------------------------------------------------
-- | Parse a value to events, or fail the test with the error message.
parseEventsOrFail :: Text -> IO [JSONEvent]
parseEventsOrFail input = case parseValueEvents input of
Left err -> expectationFailure (toString err) >> pure []
Right events -> pure events
parseValueEventsSpec :: Spec
parseValueEventsSpec = describe "parseValueEvents" $ do
it "preserves object member order" $ do
events <- parseEventsOrFail "{\"b\":1,\"a\":2}"
events
`shouldBe` [ JSONBeginObject,
JSONObjectKey "b",
JSONNumber 1,
JSONObjectKey "a",
JSONNumber 2,
JSONEndObject
]
it "produces the same events as the streaming decoder" $ do
let input = "{\"users\":[{\"name\":\"alice\",\"age\":30}],\"count\":2}"
case (parseValueEvents input, decodeChunks [input]) of
(Left err, _) -> expectationFailure (toString err)
(_, Left err) -> expectationFailure (show err)
(Right events, Right decoded) -> events `shouldBe` decoded
it "converts scalars, arrays and nested containers" $ do
events <- parseEventsOrFail "[1,\"x\",true,null,{\"k\":[]}]"
events
`shouldBe` [ JSONBeginArray,
JSONNumber 1,
JSONString "x",
JSONBool True,
JSONNull,
JSONBeginObject,
JSONObjectKey "k",
JSONBeginArray,
JSONEndArray,
JSONEndObject,
JSONEndArray
]
it "rejects trailing input"
$ parseValueEvents "[1,2] x"
`shouldSatisfy` isLeft
it "rejects malformed input"
$ parseValueEvents "{\"a\":}"
`shouldSatisfy` isLeft
it "agrees with the streaming decoder on escapes" $ do
let input = "{\"k\\u0041\":\"a\\tb\\/c\\uD83D\\uDE00\"}"
case (parseValueEvents input, decodeChunks [input]) of
(Left err, _) -> expectationFailure (toString err)
(_, Left err) -> expectationFailure (show err)
(Right events, Right decoded) -> events `shouldBe` decoded