wireform-proto-0.2.0.0: test-integration/Test/Resolver.hs
module Test.Resolver (resolverTests) where
import Control.Monad qualified
import Data.ByteString qualified as BS
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Data.Text.Encoding qualified as TE
import Proto.IDL.AST
import Proto.IDL.Parser.Resolver
import System.Directory (
createDirectoryIfMissing,
doesDirectoryExist,
removeDirectoryRecursive,
)
import System.FilePath ((</>))
import Test.Syd
resolverTests :: Spec
resolverTests =
describe
"Proto.IDL.Parser.Resolver"
$ sequence_
[ it "resolve simple proto with no imports" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "simple.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package test;"
, "message Foo {"
, " string name = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "simple.proto")
case result of
Left e -> expectationFailure ("resolve failed: " <> show e)
Right rp -> do
rpPath rp `shouldBe` dir </> "simple.proto"
Map.null (rpImports rp) `shouldBe` True
protoPackage (rpFile rp) `shouldBe` Just "test"
, it "resolve proto with local import" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "dep.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package dep;"
, "message Bar {"
, " int32 id = 1;"
, "}"
]
writeProtoFile (dir </> "main.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package main;"
, "import \"dep.proto\";"
, "message Foo {"
, " string name = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "main.proto")
case result of
Left e -> expectationFailure ("resolve failed: " <> show e)
Right rp -> do
Map.size (rpImports rp) `shouldBe` 1
(Map.member "dep.proto" (rpImports rp)) `shouldBe` True
case Map.lookup "dep.proto" (rpImports rp) of
Nothing -> expectationFailure "dep.proto not in imports"
Just dep -> protoPackage (rpFile dep) `shouldBe` Just "dep"
, it "resolve proto with subdirectory import" $ do
withTempProtoDir $ \dir -> do
createDirectoryIfMissing True (dir </> "sub")
writeProtoFile (dir </> "sub" </> "dep.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package sub;"
, "message Inner {"
, " bool flag = 1;"
, "}"
]
writeProtoFile (dir </> "main.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "import \"sub/dep.proto\";"
, "message Outer {"
, " string val = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "main.proto")
case result of
Left e -> expectationFailure ("resolve failed: " <> show e)
Right rp -> do
(Map.member "sub/dep.proto" (rpImports rp)) `shouldBe` True
, it "well-known import google/protobuf/timestamp.proto resolves" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "with_ts.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "import \"google/protobuf/timestamp.proto\";"
, "message Event {"
, " string name = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "with_ts.proto")
case result of
Left e -> expectationFailure ("resolve failed: " <> show e)
Right rp -> do
(Map.member "google/protobuf/timestamp.proto" (rpImports rp)) `shouldBe` True
let tsProto = rpImports rp Map.! "google/protobuf/timestamp.proto"
protoPackage (rpFile tsProto) `shouldBe` Just "google.protobuf"
, it "types from imported files available via registry" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "dep.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package mypkg;"
, "message Payload {"
, " bytes data = 1;"
, "}"
]
writeProtoFile (dir </> "main.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "package mypkg;"
, "import \"dep.proto\";"
, "message Request {"
, " string id = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "main.proto")
case result of
Left e -> expectationFailure ("resolve failed: " <> show e)
Right rp -> do
let importedProtos = rpImports rp
(Map.member "dep.proto" importedProtos) `shouldBe` True
let depPf = rpFile (importedProtos Map.! "dep.proto")
topLevels = protoTopLevels depPf
any isPayloadMsg topLevels `shouldBe` True
, it "missing import returns FileNotFound" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "bad.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "import \"nonexistent.proto\";"
, "message Foo {"
, " int32 x = 1;"
, "}"
]
result <- resolveProtoImports [dir] (dir </> "bad.proto")
case result of
Left (FileNotFound {}) -> pure ()
Left e -> expectationFailure ("unexpected error: " <> show e)
Right _ -> expectationFailure "expected FileNotFound error"
, it "circular import detected" $ do
withTempProtoDir $ \dir -> do
writeProtoFile (dir </> "a.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "import \"b.proto\";"
, "message A { int32 x = 1; }"
]
writeProtoFile (dir </> "b.proto") $
T.unlines
[ "syntax = \"proto3\";"
, "import \"a.proto\";"
, "message B { int32 y = 1; }"
]
result <- resolveProtoImports [dir] (dir </> "a.proto")
case result of
Left (CircularImport _) -> pure ()
Left e -> expectationFailure ("unexpected error: " <> show e)
Right _ -> expectationFailure "expected CircularImport error"
, it "getBundledIncludeDir returns valid path" $ do
dir <- getBundledIncludeDir
(not (null dir)) `shouldBe` True
]
isPayloadMsg :: TopLevel -> Bool
isPayloadMsg (TLMessage m) = msgName m == "Payload"
isPayloadMsg _ = False
withTempProtoDir :: (FilePath -> IO ()) -> IO ()
withTempProtoDir action = do
let dir = "/tmp/wireform-test-resolver"
exists <- doesDirectoryExist dir
Control.Monad.when exists $ removeDirectoryRecursive dir
createDirectoryIfMissing True dir
action dir
removeDirectoryRecursive dir
writeProtoFile :: FilePath -> T.Text -> IO ()
writeProtoFile path content = BS.writeFile path (TE.encodeUtf8 content)