packages feed

hjsonschema-0.10.0.0: test/Shared.hs

{-# LANGUAGE DeriveGeneric #-}

module Shared where

import           Control.Applicative
import           Control.Monad
import           Data.Aeson
import           Data.Aeson.TH
import qualified Data.ByteString.Lazy   as LBS
import           Data.Char              (toLower)
import           Data.Monoid
import           Data.Text              (Text)
import qualified Data.Text              as T
import           GHC.Generics
import           System.FilePath        ((</>))
import           Test.Tasty             (TestTree)
import qualified Test.Tasty.HUnit       as HU

import qualified Data.JsonSchema.Draft4 as D4

isLocal :: String -> Bool
isLocal file = (file /= "definitions.json")
            && (file /= "ref.json")
            && (file /= "refRemote.json")

data SchemaTest = SchemaTest
  { _stDescription :: Text
  , _stSchema      :: D4.Schema
  , _stCases       :: [SchemaTestCase]
  }

data SchemaTestCase = SchemaTestCase
  { _scDescription :: Text
  , _scData        :: Value
  , _scValid       :: Bool
  } deriving Generic

instance FromJSON SchemaTest where
  parseJSON = withObject "SchemaTest" $ \o -> SchemaTest
    <$> o .: "description"
    <*> o .: "schema"
    <*> o .: "tests" -- Perhaps "cases" would have been a more descriptive key.

instance FromJSON SchemaTestCase where
  parseJSON = genericParseJSON defaultOptions { fieldLabelModifier = fmap toLower . drop 3 }

readSchemaTests :: String -> [String] -> IO [SchemaTest]
readSchemaTests dir jsonFiles = concatMapM fileToCases jsonFiles
  where
    -- Each file contains an array of SchemaTests, not just one.
    fileToCases :: String -> IO [SchemaTest]
    fileToCases name = do
      let fullPath = dir </> name
      jsonBS <- LBS.readFile fullPath
      case eitherDecode jsonBS of
        Left e -> fail $ "couldn't parse file '" <> fullPath <> "': " <> e
        Right schemaTests -> pure $ prependFileName name <$> schemaTests

    prependFileName :: String -> SchemaTest -> SchemaTest
    prependFileName fileName s = s
      { _stDescription = T.pack fileName <> ": " <> _stDescription s
      }

    concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b]
    concatMapM f xs = liftM concat (mapM f xs)

toTest :: SchemaTest -> TestTree
toTest st =
  HU.testCase (T.unpack (_stDescription st)) $ do
    forM_ (_stCases st) $ \sc -> do
      g <- assertRight =<< D4.referencesViaHTTP (D4.SchemaWithURI (_stSchema st) Nothing)
      validate <- assertRight . D4.checkSchema g $ D4.SchemaWithURI (_stSchema st) Nothing
      let res = validate (_scData sc)
      if _scValid sc
        then assertValid   sc res
        else assertInvalid sc res

assertValid :: SchemaTestCase -> [D4.Invalid] -> HU.Assertion
assertValid _ [] = pure ()
assertValid sc errs =
  HU.assertFailure $ unlines
    [ "    Failed to validate data"
    , "    Description: "         <> T.unpack (_scDescription sc)
    , "    Data: "                <> show (_scData sc)
    , "    Validation failures: " <> show errs
    ]

assertInvalid :: SchemaTestCase -> [D4.Invalid] -> HU.Assertion
assertInvalid sc [] =
  HU.assertFailure $ unlines
    [ "    Validated invalid data"
    , "    Description: " <> T.unpack (_scDescription sc)
    , "    Data: "        <> show (_scData sc)
    ]
assertInvalid _ _ = pure ()

assertRight :: (Show a) => Either a b -> IO b
assertRight (Left e)  = HU.assertFailure (show e) >> fail "assertRight failed"
assertRight (Right b) = pure b