{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Lib 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 qualified Data.HashMap.Strict as H import Data.JsonSchema import Data.Monoid import Data.Text (Text) import qualified Data.Text as T import Data.Vector (Vector) import System.FilePath (()) import Test.Framework (Test) import Test.Framework.Providers.HUnit (testCase) import Test.HUnit hiding (Test) isLocal :: String -> Bool isLocal file = (file /= "definitions.json") && (file /= "ref.json") && (file /= "refRemote.json") data SchemaTest = SchemaTest { _stDescription :: Text , _stSchema :: RawSchema , _stCases :: [SchemaTestCase] } data SchemaTestCase = SchemaTestCase { _scDescription :: Text , _scData :: Value , _scValid :: Bool } instance FromJSON RawSchema where parseJSON = withObject "Schema" $ \o -> return RawSchema { _rsURI = "", _rsObject = o } instance FromJSON SchemaTest where parseJSON = withObject "SchemaTest" $ \o -> SchemaTest <$> o .: "description" <*> o .: "schema" <*> o .: "tests" -- I wish this were "cases" 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 -> return $ 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 -> Test toTest st = testCase (T.unpack $ _stDescription st) $ do void . assertRight . isValidSchema $ _stSchema st forM_ (_stCases st) $ \sc -> do g <- assertRight =<< fetchRefs draft4 (_stSchema st) H.empty let res = validate (compile draft4 g $ _stSchema st) (_scData sc) if _scValid sc then assertValid sc res else assertInvalid sc res assertValid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion assertValid sc (Left es) = assertFailure $ unlines [ " Failed to validate data" , " Description: " <> T.unpack (_scDescription sc) , " Data: " <> show (_scData sc) , " Errors: " <> show es ] assertValid _ _ = return () assertInvalid :: SchemaTestCase -> Either (Vector ValErr) Value -> Assertion assertInvalid sc (Right _) = assertFailure $ unlines [ " Failed to invalidate data" , " Description: " <> T.unpack (_scDescription sc) , " Data: " <> show (_scData sc) ] assertInvalid _ _ = return () assertRight :: (Show a) => Either a b -> IO b assertRight a = case a of Left e -> assertFailure (show e) >> fail "assertRight failed" Right b -> return b $(deriveFromJSON defaultOptions { fieldLabelModifier = map toLower . drop 3 } ''SchemaTestCase)