proto3-suite-0.9.4: tests/TestCodeGen.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module TestCodeGen where
import ArbitraryGeneratedTestTypes ()
import Control.Applicative
import Control.Monad
#ifdef SWAGGER
import qualified Data.Aeson
#endif
import qualified Data.ByteString.Lazy as LBS
import Data.Proxy (Proxy(..))
import Data.String (IsString)
#ifdef SWAGGER
import Data.Swagger (ToSchema)
import qualified Data.Swagger
#endif
import qualified Data.Text as T
import Data.Typeable (Typeable, typeRep,
#ifdef SWAGGER
splitTyConApp, tyConName
#endif
)
import GHC.Exts (Proxy#, proxy#)
#ifdef SWAGGER
import GHC.Stack (HasCallStack)
#endif
import Google.Protobuf.Timestamp (Timestamp(..))
import Prelude hiding (FilePath)
import Proto3.Suite.Class (Message(..), def)
import Proto3.Suite.DotProto (fieldLikeName, prefixedEnumFieldName, typeLikeName)
import Proto3.Suite.DotProto.AST (DotProtoField(..), DotProtoIdentifier(..),
DotProtoType(..), DotProtoPrimType(..))
import Proto3.Suite.DotProto.Generate
import Proto3.Suite.Haskell.Parser (Logger)
import Proto3.Suite.JSONPB (FromJSONPB (..), Options (..),
ToJSONPB (..), defaultOptions,
eitherDecode, encode,
jsonPBOptions)
import Proto3.Suite.Types (Enumerated(..))
import System.Exit
import Test.Proto.ToEncoder (Iterator, Stripping)
import Test.Tasty
import Test.Tasty.HUnit (testCase, (@?=))
import Test.Tasty.QuickCheck (Arbitrary, (===), testProperty)
import qualified Turtle
import qualified Turtle.Format as F
import qualified TestProto
import qualified TestProtoNegativeEnum
import qualified TestProtoOneof
import qualified TestProtoOptional
#ifdef SWAGGER
import qualified TestProtoWrappers
#endif
codeGenTests :: Logger -> TestTree
codeGenTests logger = testGroup "Code generator unit tests"
[ dotProtoTests
, jsonpbTests
, hasDefaultTests
, pascalCaseMessageNames
, camelCaseMessageFieldNames
, don'tAlterEnumFieldNames
, knownTypeMessages
, pythonInteroperation logger
#ifdef SWAGGER
, swaggerTests
, swaggerWrapperFormat
#endif
]
pythonInteroperation :: Logger -> TestTree
pythonInteroperation logger = testGroup "Python interoperation" $ do
tt <- ["Data.Text.Lazy.Text", "Data.Text.Text", "Data.Text.Short.ShortText"]
format <- ["Binary", "Jsonpb"]
testEncode <- [True, False]
direct <- [False, True]
guard $ not direct || (testEncode && format == "Binary")
let f = if testEncode then simpleEncodeDotProto direct else simpleDecodeDotProto
pure @[] (f logger tt format)
#ifdef SWAGGER
swaggerWrapperFormat :: TestTree
swaggerWrapperFormat = testGroup "Swagger Wrapper Format"
[ expectSchema @TestProtoWrappers.TestDoubleValue
"{\"properties\":{\"wrapper\":{\"format\":\"DoubleValue\",\"type\":\"number\"},\"many\":{\"items\":{\"format\":\"double\",\"type\":\"number\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestDoubleValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"double\",\"type\":\"number\"},\"many\":{\"items\":{\"format\":\"double\",\"type\":\"number\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestDoubleValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestFloatValue
"{\"properties\":{\"wrapper\":{\"format\":\"FloatValue\",\"type\":\"number\"},\"many\":{\"items\":{\"format\":\"float\",\"type\":\"number\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestFloatValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"float\",\"type\":\"number\"},\"many\":{\"items\":{\"format\":\"float\",\"type\":\"number\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestFloatValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestInt64Value
"{\"properties\":{\"wrapper\":{\"format\":\"Int64Value\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestInt64ValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestInt64ValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestUInt64Value
"{\"properties\":{\"wrapper\":{\"format\":\"UInt64Value\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int64\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestUInt64ValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"int64\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int64\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestUInt64ValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestInt32Value
"{\"properties\":{\"wrapper\":{\"format\":\"Int32Value\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestInt32ValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestInt32ValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestUInt32Value
"{\"properties\":{\"wrapper\":{\"format\":\"UInt32Value\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int32\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestUInt32ValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"int32\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"},\"many\":{\"items\":{\"format\":\"int32\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestUInt32ValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestBoolValue
"{\"properties\":{\"wrapper\":{\"format\":\"BoolValue\",\"type\":\"boolean\"},\"many\":{\"items\":{\"type\":\"boolean\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestBoolValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"type\":\"boolean\"},\"many\":{\"items\":{\"type\":\"boolean\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestBoolValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestStringValue
"{\"properties\":{\"wrapper\":{\"format\":\"StringValue\",\"type\":\"string\"},\"many\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestStringValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"type\":\"string\"},\"many\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestStringValuePickOne\"}},\"type\":\"object\"}"
, expectSchema @TestProtoWrappers.TestBytesValue
"{\"properties\":{\"wrapper\":{\"format\":\"BytesValue\",\"type\":\"string\"},\"many\":{\"items\":{\"format\":\"byte\",\"type\":\"string\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestBytesValuePickOne\"}},\"type\":\"object\"}"
"{\"properties\":{\"wrapper\":{\"format\":\"byte\",\"type\":\"string\"},\"many\":{\"items\":{\"format\":\"byte\",\"type\":\"string\"},\"type\":\"array\"},\"pickOne\":{\"$ref\":\"#/definitions/TestBytesValuePickOne\"}},\"type\":\"object\"}"
]
where
expectSchema ::
forall a .
(ToSchema a, Typeable a, HasCallStack) =>
LBS.ByteString ->
LBS.ByteString ->
TestTree
expectSchema wrapperFormat noWrapperFormat =
testCase (tyConName (fst (splitTyConApp (typeRep (Proxy @a))))) $ do
lbsSchemaOf @a @?= (if wf then wrapperFormat else noWrapperFormat)
where
wf :: Bool
#ifdef SWAGGER_WRAPPER_FORMAT
wf = True
#else
wf = False
#endif
#endif
knownTypeMessages :: TestTree
knownTypeMessages =
testGroup
"KnownType custom codec"
[ testCase "Timestamp rfc3339 json encoding"
$ encode defaultOptions (Timestamp 0 0) @?= "\"1970-01-01T00:00:00Z\""
, testCase "Timestamp rfc3339 json decoding"
$ eitherDecode "\"1970-01-01T00:00:00Z\"" @?= Right (Timestamp 0 0)
]
pascalCaseMessageNames :: TestTree
pascalCaseMessageNames = testGroup "PascalCasing of message names"
[ testCase "Capitalizes letters after underscores"
$ typeLikeName "protocol_analysis" @?= Right "ProtocolAnalysis"
, testCase "Preserves casing of interior letters"
$ typeLikeName "analyze_HTTP" @?= Right "AnalyzeHTTP"
, testCase "Handles non-alphanumeric characters after underscore"
$ typeLikeName "analyze_http_2" @?= Right "AnalyzeHttp2"
, testCase "Preserves one underscore in double underscore sequence"
$ typeLikeName "Analyze__HTTP" @?= Right "Analyze_HTTP"
, testCase "Handles names prefixed with underscore"
$ typeLikeName "_message_name" @?= Right "XMessageName"
, testCase "Preserves trailing underscore"
$ typeLikeName "message_name_" @?= Right "MessageName_"
]
camelCaseMessageFieldNames :: TestTree
camelCaseMessageFieldNames = testGroup "camelCasing of field names"
[ testCase "Preserves capitalization patterns"
$ fieldLikeName "IP" @?= "ip"
, testCase "Preserves underscores"
$ fieldLikeName "IP_address" @?= "ip_address"
]
don'tAlterEnumFieldNames :: TestTree
don'tAlterEnumFieldNames
= testGroup "Do not alter enumeration field names"
$ tc <$> [ "fnord"
, "FNORD"
, "PascalCase"
, "camelCase"
, "VOCIFEROUS_SNAKE_CASE"
, "snake_case"
, "snake_case_"
]
where
enumName = "MyEnum"
tc fieldName = testCase fieldName $
prefixedEnumFieldName enumName fieldName @?= (enumName <> fieldName)
setPythonPath :: IO ()
setPythonPath = Turtle.export "PYTHONPATH" .
maybe pyTmpDir (\p -> pyTmpDir <> ":" <> p) =<< Turtle.need "PYTHONPATH"
simpleEncodeDotProto :: Bool -> Logger -> String -> T.Text -> TestTree
simpleEncodeDotProto direct logger chosenStringType format =
testCase ("generate code for a simple .proto and then use it to encode messages" ++
" with string type " ++ chosenStringType ++ " in format " ++ show format ++
(if direct then ", direct mode" else ", intermediate mode"))
$ do
decodedStringType <- either die pure (parseStringType chosenStringType)
compileTestDotProtos logger decodedStringType direct
-- Compile our generated encoder
let encodeCmd = "tests/encode.sh " <> hsTmpDir
<> (if direct then " -DTYPE_LEVEL_FORMAT" else "")
#if DHALL
<> " -DDHALL"
#endif
Turtle.shell encodeCmd empty >>= (@?= ExitSuccess)
-- The python test of encoding exits with a special error code to indicate
-- all tests were successful. When directly encoding without an intermediate
-- data structure, we run the test several times (still compiling just once)
-- in order to cover various supported ways of iterating over repeated fields.
setPythonPath
let iterators :: [Iterator]
iterators
| direct = [minBound .. maxBound]
| otherwise = [minBound] -- Just an unused placeholder
strippings :: [Stripping]
strippings
| direct = [minBound .. maxBound]
| otherwise = [minBound] -- Just an unused placeholder
forM_ iterators $ \(iterator :: Iterator) -> do
forM_ strippings $ \(stripping :: Stripping) -> do
when direct $ do
putStrLn $ " iterator: " ++ show iterator
putStrLn $ " stripping: " ++ show stripping
let cmd = hsTmpDir <> "/simpleEncodeDotProto " <> format <>
" " <> T.pack (show iterator) <> " " <> T.pack (show stripping) <>
" | python tests/check_simple_dot_proto.py " <> format
Turtle.shell cmd empty >>= (@?= ExitFailure 12)
-- Not using bracket so that we can inspect the output to fix the tests
Turtle.rmtree hsTmpDir
Turtle.rmtree pyTmpDir
simpleDecodeDotProto :: Logger -> String -> T.Text -> TestTree
simpleDecodeDotProto logger chosenStringType format =
testCase ("generate code for a simple .proto and then use it to decode messages" ++
" with string type " ++ chosenStringType ++ " in format " ++ show format)
$ do
decodedStringType <- either die pure (parseStringType chosenStringType)
compileTestDotProtos logger decodedStringType False
-- Compile our generated decoder
let decodeCmd = "tests/decode.sh " <> hsTmpDir
#if DHALL
<> " -DDHALL"
#endif
Turtle.shell decodeCmd empty >>= (@?= ExitSuccess)
setPythonPath
let cmd = "python tests/send_simple_dot_proto.py " <> format <> " | FORMAT=" <> format <> " " <> hsTmpDir <> "/simpleDecodeDotProto "
Turtle.shell cmd empty >>= (@?= ExitSuccess)
-- Not using bracket so that we can inspect the output to fix the tests
Turtle.rmtree hsTmpDir
Turtle.rmtree pyTmpDir
-- * Helpers
hsTmpDir, pyTmpDir :: IsString a => a
hsTmpDir = "test-files/hs-tmp"
pyTmpDir = "test-files/py-tmp"
defaultStringType :: StringType
defaultStringType = StringType "Data.Text.Lazy" "Text"
compileTestDotProtos :: Logger -> StringType -> Bool -> IO ()
compileTestDotProtos logger decodedStringType typeLevel = do
Turtle.mktree hsTmpDir
Turtle.mktree pyTmpDir
let protoFiles :: [Turtle.FilePath]
protoFiles =
[ "test_proto.proto"
, "test_proto_import.proto"
, "test_proto_negative_enum.proto"
, "test_proto_oneof.proto"
, "test_proto_oneof_import.proto"
{- These tests have been temporarily removed to pass CI.
, "test_proto_leading_dot.proto"
, "test_proto_protoc_plugin.proto"
-}
, "test_proto_nested_message.proto"
, "test_proto_wrappers.proto"
, "test_proto_negative_enum.proto"
, "test_proto_optional.proto"
]
forM_ protoFiles $ \protoFile -> do
compileDotProtoFileOrDie logger
CompileArgs{ includeDir = ["test-files"]
, extraInstanceFiles = ["test-files" Turtle.</> "Orphan.hs"]
, outputDir = hsTmpDir
, inputProto = protoFile
, stringType = decodedStringType
, typeLevelFormat = typeLevel
}
let cmd = T.concat [ "protoc --python_out="
, pyTmpDir
, " --proto_path=test-files"
, " test-files/" <> Turtle.format F.fp protoFile
]
Turtle.shell cmd empty >>= (@?= ExitSuccess)
Turtle.touch (pyTmpDir Turtle.</> "__init__.py")
dotProtoTests :: TestTree
dotProtoTests = testGroup "dotProto method tests"
[ dotProtoTest @TestProto.Trivial
[ DotProtoField 1 (Prim Int32) (Single "trivialField") [] ""
]
, dotProtoTest @TestProto.MultipleFields
[ DotProtoField 1 (Prim Double) (Single "multiFieldDouble") [] ""
, DotProtoField 2 (Prim Float) (Single "multiFieldFloat") [] ""
, DotProtoField 3 (Prim Int32) (Single "multiFieldInt32") [] ""
, DotProtoField 4 (Prim Int64) (Single "multiFieldInt64") [] ""
, DotProtoField 5 (Prim String) (Single "multiFieldString") [] ""
, DotProtoField 6 (Prim Bool) (Single "multiFieldBool") [] ""
]
, dotProtoTest @TestProto.SignedInts
[ DotProtoField 1 (Prim SInt32) (Single "signed32") [] ""
, DotProtoField 2 (Prim SInt64) (Single "signed64") [] ""
]
, dotProtoTest @TestProto.MapTest
[ DotProtoField 1 (Map String SInt32) (Single "prim") [] ""
, DotProtoField 2 (Map Int32 (Named (Single "WrappedTrivial"))) (Single "trivial") [] ""
, DotProtoField 3 (Map SInt32 SInt32) (Single "signed") [] ""
-- Current parser discards comments.
]
, dotProtoTest @TestProto.WithNestingRepeatedInts
[ DotProtoField 1 (Repeated (Named (Single "NestedInts"))) (Single "nestedInts") [] ""
]
, dotProtoTest @TestProto.WithRepeatedSigned
[ DotProtoField 1 (Repeated SInt32) (Single "r32") [] ""
, DotProtoField 2 (Repeated SInt64) (Single "r64") [] ""
]
, dotProtoTest @TestProtoOptional.WithOptional
[ DotProtoField 10 (Optional Double) (Single "optionalDouble") [] ""
, DotProtoField 20 (Optional Float) (Single "optionalFloat") [] ""
, DotProtoField 30 (Optional Int32) (Single "optionalInt32") [] ""
, DotProtoField 40 (Optional Int64) (Single "optionalInt64") [] ""
, DotProtoField 50 (Optional UInt32) (Single "optionalUint32") [] ""
, DotProtoField 60 (Optional UInt64) (Single "optionalUint64") [] ""
, DotProtoField 70 (Optional SInt32) (Single "optionalSint32") [] ""
, DotProtoField 80 (Optional SInt64) (Single "optionalSint64") [] ""
, DotProtoField 90 (Optional Fixed32) (Single "optionalFixed32") [] ""
, DotProtoField 100 (Optional Fixed64) (Single "optionalFixed64") [] ""
, DotProtoField 110 (Optional SFixed32) (Single "optionalSfixed32") [] ""
, DotProtoField 120 (Optional SFixed64) (Single "optionalSfixed64") [] ""
, DotProtoField 130 (Optional Bool) (Single "optionalBool") [] ""
, DotProtoField 140 (Optional String) (Single "optionalString") [] ""
, DotProtoField 150 (Optional Bytes) (Single "optionalBytes") [] ""
, DotProtoField 160 (Optional (Named (Single "Enum"))) (Single "optionalEnum") [] ""
, DotProtoField 170 (Optional (Named (Single "Submessage"))) (Single "optionalSubmessage") [] ""
]
]
jsonpbTests :: TestTree
jsonpbTests = testGroup "JSONPB tests"
[ testGroup "Round-trip tests"
[ roundTripTest @TestProto.Trivial
, roundTripTest @TestProto.MultipleFields
, roundTripTest @TestProto.SignedInts
, testProperty "roundTrip (SignedInts minBound minBound)" $
roundTrip (TestProto.SignedInts minBound minBound)
, testProperty "roundTrip (SignedInts maxBound maxBound)" $
roundTrip (TestProto.SignedInts maxBound maxBound)
, testProperty "roundTrip . WithEnum . Enumerated . Right" $
roundTrip . TestProto.WithEnum . Enumerated . Right
, roundTripTest @TestProto.WithNesting
, roundTripTest @TestProto.WithNestingRepeated
, roundTripTest @TestProto.WithNestingRepeatedInts
, roundTripTest @TestProto.WithBytes
, roundTripTest @TestProto.OutOfOrderFields
, roundTripTest @TestProto.UsingImported
, roundTripTest @TestProto.Wrapped
, roundTripTest @TestProtoOneof.Something
, roundTripTest @TestProtoOneof.WithImported
, roundTripTest @TestProtoNegativeEnum.WithNegativeEnum
, roundTripTest @TestProtoOptional.WithOptional
]
, testGroup "Specific encoding tests" $
let jsonPB = jsonPBOptions
json = defaultOptions
in
[ encodesAs jsonPB (TestProto.MultipleFields 0 0 0 0 "" False) "{}"
, encodesAs json (TestProto.MultipleFields 0 2.0 0 0 "" True) "{\"multiFieldDouble\":0.0,\"multiFieldFloat\":2.0,\"multiFieldInt32\":0,\"multiFieldInt64\":\"0\",\"multiFieldString\":\"\",\"multiFieldBool\":true}"
, encodesAs jsonPB (TestProto.SignedInts minBound minBound)
"{\"signed32\":-2147483648,\"signed64\":\"-9223372036854775808\"}"
, encodesAs jsonPB (TestProto.SignedInts maxBound maxBound)
"{\"signed32\":2147483647,\"signed64\":\"9223372036854775807\"}"
, encodesAs jsonPB (TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM1)))
"{}"
, encodesAs json (TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM1)))
"{\"enumField\":\"ENUM1\"}"
, encodesAs jsonPB (TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM3)))
"{\"enumField\":\"ENUM3\"}"
, encodesAs jsonPB (TestProto.WithNesting $ Just $ TestProto.WithNesting_Nested "" 0 [1,2] [66,99])
"{\"nestedMessage\":{\"nestedPacked\":[1,2],\"nestedUnpacked\":[66,99]}}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneName "")))
"{\"value\":\"42\",\"another\":99,\"name\":\"\"}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneSomeid 0)))
"{\"value\":\"42\",\"another\":99,\"someid\":0}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg1 (TestProtoOneof.DummyMsg 66))))
"{\"value\":\"42\",\"another\":99,\"dummyMsg1\":{\"dummy\":66}}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg2 (TestProtoOneof.DummyMsg 67))))
"{\"value\":\"42\",\"another\":99,\"dummyMsg2\":{\"dummy\":67}}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyEnum (Enumerated (Right TestProtoOneof.DummyEnumDUMMY0)))))
"{\"value\":\"42\",\"another\":99,\"dummyEnum\":\"DUMMY0\"}"
, encodesAs jsonPB (TestProtoOneof.Something 42 99 Nothing)
"{\"value\":\"42\",\"another\":99}"
, encodesAs json (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneName "")))
"{\"value\":\"42\",\"another\":99,\"pickOne\":{\"name\":\"\"}}"
, encodesAs json (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneSomeid 0)))
"{\"value\":\"42\",\"another\":99,\"pickOne\":{\"someid\":0}}"
, encodesAs json (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg1 (TestProtoOneof.DummyMsg 66))))
"{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyMsg1\":{\"dummy\":66}}}"
, encodesAs json (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg2 (TestProtoOneof.DummyMsg 67))))
"{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyMsg2\":{\"dummy\":67}}}"
, encodesAs json (TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyEnum (Enumerated (Right TestProtoOneof.DummyEnumDUMMY0)))))
"{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyEnum\":\"DUMMY0\"}}"
, encodesAs json (TestProtoOneof.Something 42 99 Nothing)
"{\"value\":\"42\",\"another\":99,\"pickOne\":null}"
]
, testGroup "Specific decoding tests" $
[ decodesAs "{\"signed32\":2147483647,\"signed64\":\"9223372036854775807\"}"
(TestProto.SignedInts 2147483647 9223372036854775807)
, decodesAs "{\"enumField\":\"ENUM3\"}" (TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM3)))
, decodesAs "{\"enumField\":null}"
(TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM1)))
, decodesAs "{}"
(TestProto.WithEnum (Enumerated (Right TestProto.WithEnum_TestEnumENUM1)))
, decodesAs "{\"nestedMessage\":{}}"
(TestProto.WithNesting $ Just $ TestProto.WithNesting_Nested "" 0 [] [])
, testGroup "JSONPB"
[ decodesAs "{\"value\":\"42\",\"another\":99,\"someid\":66}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneSomeid 66)))
, decodesAs "{\"value\":\"42\",\"another\":99,\"name\":\"foo\"}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneName "foo")))
, decodesAs "{\"value\":\"42\",\"another\":99,\"dummyMsg1\":{\"dummy\":41}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg1 (TestProtoOneof.DummyMsg 41))))
, decodesAs "{\"value\":\"42\",\"another\":99,\"dummyMsg2\":{\"dummy\":43}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg2 (TestProtoOneof.DummyMsg 43))))
, decodesAs "{\"value\":\"42\",\"another\":99,\"dummyEnum\":\"DUMMY0\"}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyEnum (Enumerated (Right TestProtoOneof.DummyEnumDUMMY0)))))
, decodesAs "{\"value\":\"42\",\"another\":99}"
(TestProtoOneof.Something 42 99 Nothing)
]
, testGroup "JSON"
[ decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{\"name\":\"\"}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneName "")))
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{\"someid\":0}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneSomeid 0)))
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyMsg1\":{\"dummy\":66}}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg1 (TestProtoOneof.DummyMsg 66))))
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyMsg2\":{\"dummy\":67}}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyMsg2 (TestProtoOneof.DummyMsg 67))))
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{\"dummyEnum\":\"DUMMY0\"}}"
(TestProtoOneof.Something 42 99 (Just (TestProtoOneof.SomethingPickOneDummyEnum (Enumerated (Right TestProtoOneof.DummyEnumDUMMY0)))))
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":{}}" (TestProtoOneof.Something 42 99 Nothing)
, decodesAs "{\"value\":\"42\",\"another\":99,\"pickOne\":null}" (TestProtoOneof.Something 42 99 Nothing)
]
]
]
#ifdef SWAGGER
swaggerTests :: TestTree
swaggerTests = testGroup "Swagger tests"
[ schemaOf @TestProtoOneof.Something
"{\"properties\":{\"value\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"},\"another\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"pickOne\":{\"$ref\":\"#/definitions/SomethingPickOne\"}},\"type\":\"object\"}"
, schemaOf @TestProtoOneof.SomethingPickOne
"{\"properties\":{\"name\":{\"type\":\"string\"},\"someid\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"dummyMsg1\":{\"$ref\":\"#/definitions/DummyMsg\"},\"dummyMsg2\":{\"$ref\":\"#/definitions/DummyMsg\"},\"dummyEnum\":{\"$ref\":\"#/definitions/DummyEnum\"}},\"maxProperties\":1,\"minProperties\":1,\"type\":\"object\"}"
, schemaOf @TestProtoOneof.DummyMsg
"{\"properties\":{\"dummy\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"}},\"type\":\"object\"}"
, schemaOf @(Enumerated TestProtoOneof.DummyEnum)
"{\"enum\":[\"DUMMY0\",\"DUMMY1\"],\"type\":\"string\"}"
, schemaOf @TestProtoOptional.WithOptional $
"{\"properties\":{" <>
"\"optionalDouble\":{\"format\":\"double\",\"type\":\"number\"}," <>
"\"optionalFloat\":{\"format\":\"float\",\"type\":\"number\"}," <>
"\"optionalInt32\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"}," <>
"\"optionalInt64\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"}," <>
"\"optionalUint32\":{\"format\":\"int32\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"}," <>
"\"optionalUint64\":{\"format\":\"int64\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"}," <>
"\"optionalSint32\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"}," <>
"\"optionalSint64\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"}," <>
"\"optionalFixed32\":{\"format\":\"int32\",\"maximum\":4294967295,\"minimum\":0,\"type\":\"integer\"}," <>
"\"optionalFixed64\":{\"format\":\"int64\",\"maximum\":18446744073709551615,\"minimum\":0,\"type\":\"integer\"}," <>
"\"optionalSfixed32\":{\"format\":\"int32\",\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"}," <>
"\"optionalSfixed64\":{\"format\":\"int64\",\"maximum\":9223372036854775807,\"minimum\":-9223372036854775808,\"type\":\"integer\"}," <>
"\"optionalBool\":{\"type\":\"boolean\"}," <>
"\"optionalString\":{\"type\":\"string\"}," <>
"\"optionalBytes\":{\"format\":\"byte\",\"type\":\"string\"}," <>
"\"optionalEnum\":{\"$ref\":\"#/definitions/Enum\"}," <>
"\"optionalSubmessage\":{\"$ref\":\"#/definitions/Submessage\"}" <>
"},\"type\":\"object\"}"
]
#endif
hasDefaultTests :: TestTree
hasDefaultTests = testGroup "Generic HasDefault"
[ testProperty "MultipleFields" $
(def :: TestProto.MultipleFields) ===
TestProto.MultipleFields {multipleFieldsMultiFieldDouble = 0.0, multipleFieldsMultiFieldFloat = 0.0, multipleFieldsMultiFieldInt32 = 0, multipleFieldsMultiFieldInt64 = 0, multipleFieldsMultiFieldString = "", multipleFieldsMultiFieldBool = False}
, testProperty "WithNesting" $
(def :: TestProto.WithNesting) ===
TestProto.WithNesting {withNestingNestedMessage = Nothing}
, testProperty "WithNestingRepeated" $
(def :: TestProto.WithNestingRepeated) ===
TestProto.WithNestingRepeated {withNestingRepeatedNestedMessages = []}
, testProperty "WithEnum" $
(def :: TestProto.WithEnum) ===
TestProto.WithEnum {withEnumEnumField = Enumerated {enumerated = Right TestProto.WithEnum_TestEnumENUM1}}
]
-- * Helper quickcheck props
dotProtoTest ::
forall a .
(Message a, Typeable a) =>
[DotProtoField] ->
TestTree
dotProtoTest expected =
testProperty ("dotProtoTest @" ++ show (typeRep (Proxy :: Proxy a))) $
dotProto (proxy# :: Proxy# a) === expected
roundTripTest ::
forall a .
(ToJSONPB a, FromJSONPB a, Eq a, Arbitrary a, Show a, Typeable a) =>
TestTree
roundTripTest =
testProperty ("roundTripTest @" ++ show (typeRep (Proxy :: Proxy a))) $
roundTrip @a
roundTrip :: (ToJSONPB a, FromJSONPB a, Eq a)
=> a -> Bool
roundTrip x = roundTrip' False && roundTrip' True
where
roundTrip' emitDefaults =
eitherDecode (encode defaultOptions{ optEmitDefaultValuedFields = emitDefaults} x)
==
Right x
encodesAs ::
forall a .
(ToJSONPB a, Eq a, Show a, Typeable a) =>
Options -> a -> LBS.ByteString -> TestTree
encodesAs opts x bs = testProperty (testName "") (encode opts x === bs)
where
testName =
showString "encode @" .
showsPrec 11 (typeRep (Proxy :: Proxy a)) .
showChar ' ' .
showsPrec 11 opts .
showChar ' ' .
showsPrec 11 x .
showString " == " .
showsPrec 5 bs
decodesAs ::
forall a .
(FromJSONPB a, Eq a, Show a, Typeable a) =>
LBS.ByteString -> a -> TestTree
decodesAs bs x = testProperty (testName "") (eitherDecode bs === Right x)
where
testName =
showString "eitherDecode @" .
showsPrec 11 (typeRep (Proxy :: Proxy a)) .
showChar ' ' .
showsPrec 11 bs .
showString " == Right " .
showsPrec 11 x
#ifdef SWAGGER
schemaOf ::
forall a .
(ToSchema a, Eq a, Show a, Typeable a) =>
LBS.ByteString -> TestTree
schemaOf bs = testProperty (testName "") (lbsSchemaOf @a === bs)
where
testName =
showString "lbsSchemaOf @" .
showsPrec 11 (typeRep (Proxy :: Proxy a)) .
showString " == " .
showsPrec 5 bs
lbsSchemaOf :: forall a . ToSchema a => LBS.ByteString
lbsSchemaOf = Data.Aeson.encode (Data.Swagger.toSchema (Proxy @a))
#endif