Merge pull request #649 from hudayou/proxy-awareness
Add proxy awareness for OpenAPI spec generation
This commit is contained in:
@@ -7,6 +7,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Ability to generate an OpenAPI spec - @mainx07, @hudayou, @ruslantalpa, @begriffs
|
- Ability to generate an OpenAPI spec - @mainx07, @hudayou, @ruslantalpa, @begriffs
|
||||||
|
- Ability to generate an OpenAPI spec behind a proxy- @hudayou
|
||||||
- Ability to set addresses to listen on - @hudayou
|
- Ability to set addresses to listen on - @hudayou
|
||||||
- Filtering, shaping and embedding with &select for the /rpc path - @ruslantalpa
|
- Filtering, shaping and embedding with &select for the /rpc path - @ruslantalpa
|
||||||
- Output names of used-defined types (instead of 'USER-DEFINED') - @martingms
|
- Output names of used-defined types (instead of 'USER-DEFINED') - @martingms
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import PostgREST.Config (AppConfig (..),
|
|||||||
minimumPgVersion,
|
minimumPgVersion,
|
||||||
prettyVersion,
|
prettyVersion,
|
||||||
readOptions)
|
readOptions)
|
||||||
|
import PostgREST.OpenAPI (isMalformedProxyUri)
|
||||||
import PostgREST.DbStructure
|
import PostgREST.DbStructure
|
||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
@@ -50,12 +51,16 @@ main = do
|
|||||||
conf <- readOptions
|
conf <- readOptions
|
||||||
let host = configHost conf
|
let host = configHost conf
|
||||||
port = configPort conf
|
port = configPort conf
|
||||||
|
proxy = configProxyUri conf
|
||||||
pgSettings = cs (configDatabase conf)
|
pgSettings = cs (configDatabase conf)
|
||||||
appSettings = setHost (fromString host)
|
appSettings = setHost (fromString host)
|
||||||
. setPort port
|
. setPort port
|
||||||
. setServerName (cs $ "postgrest/" <> prettyVersion)
|
. setServerName (cs $ "postgrest/" <> prettyVersion)
|
||||||
$ defaultSettings
|
$ defaultSettings
|
||||||
|
|
||||||
|
when (isMalformedProxyUri proxy) $ error
|
||||||
|
"Malformed proxy uri, a correct example: https://example.com:8443/basePath"
|
||||||
|
|
||||||
unless (secret "secret" /= configJwtSecret conf) $
|
unless (secret "secret" /= configJwtSecret conf) $
|
||||||
putStrLn "WARNING, running in insecure mode, JWT secret is the default value"
|
putStrLn "WARNING, running in insecure mode, JWT secret is the default value"
|
||||||
Prelude.putStrLn $ "Listening on port " ++
|
Prelude.putStrLn $ "Listening on port " ++
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ executable postgrest
|
|||||||
, warp >= 3.1.0
|
, warp >= 3.1.0
|
||||||
, insert-ordered-containers >= 0.1.0.1
|
, insert-ordered-containers >= 0.1.0.1
|
||||||
, swagger2 >= 2.1
|
, swagger2 >= 2.1
|
||||||
|
, network-uri >= 2.6.1.0
|
||||||
, HTTP
|
, HTTP
|
||||||
, Ranged-sets
|
, Ranged-sets
|
||||||
, protolude >= 0.1.5 && < 0.2.0
|
, protolude >= 0.1.5 && < 0.2.0
|
||||||
@@ -115,6 +116,7 @@ library
|
|||||||
, insert-ordered-containers >= 0.1.0.1
|
, insert-ordered-containers >= 0.1.0.1
|
||||||
, swagger2 >= 2.1
|
, swagger2 >= 2.1
|
||||||
, protolude >= 0.1.5 && < 0.2.0
|
, protolude >= 0.1.5 && < 0.2.0
|
||||||
|
, network-uri >= 2.6.1.0
|
||||||
|
|
||||||
Other-Modules: Paths_postgrest
|
Other-Modules: Paths_postgrest
|
||||||
Exposed-Modules: PostgREST.App
|
Exposed-Modules: PostgREST.App
|
||||||
@@ -197,5 +199,6 @@ Test-Suite spec
|
|||||||
, hjsonpointer
|
, hjsonpointer
|
||||||
, hjsonschema
|
, hjsonschema
|
||||||
, swagger2
|
, swagger2
|
||||||
|
, network-uri
|
||||||
, HTTP
|
, HTTP
|
||||||
, Ranged-sets
|
, Ranged-sets
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Data.List (find, delete)
|
|||||||
import Data.Maybe (fromMaybe, fromJust, mapMaybe)
|
import Data.Maybe (fromMaybe, fromJust, mapMaybe)
|
||||||
import Data.Ranged.Ranges (emptyRange)
|
import Data.Ranged.Ranges (emptyRange)
|
||||||
import Data.String.Conversions (cs)
|
import Data.String.Conversions (cs)
|
||||||
import Data.Text (Text, replace, strip, isInfixOf, dropWhile, drop)
|
import Data.Text (Text, replace, strip, pack, isInfixOf, dropWhile, drop)
|
||||||
import Data.Tree
|
import Data.Tree
|
||||||
|
|
||||||
import qualified Hasql.Pool as P
|
import qualified Hasql.Pool as P
|
||||||
@@ -204,9 +204,13 @@ app dbStructure conf apiRequest =
|
|||||||
else cs $ encode body)
|
else cs $ encode body)
|
||||||
|
|
||||||
(ActionRead, TargetRoot, Nothing) -> do
|
(ActionRead, TargetRoot, Nothing) -> do
|
||||||
let encodeApi ti = encodeOpenAPI ti host port
|
let encodeApi ti = encodeOpenAPI ti uri'
|
||||||
host = configHost conf
|
host = configHost conf
|
||||||
port = toInteger $ configPort conf
|
port = toInteger $ configPort conf
|
||||||
|
proxy = pickProxy $ configProxyUri conf
|
||||||
|
uri Nothing = ("http", pack host, port, "/")
|
||||||
|
uri (Just Proxy { proxyScheme = s, proxyHost = h, proxyPort = p, proxyPath = b }) = (s, h, p, b)
|
||||||
|
uri' = uri proxy
|
||||||
encodeFn = if contentType == OpenAPI then encodeApi . toTableInfo else encode
|
encodeFn = if contentType == OpenAPI then encodeApi . toTableInfo else encode
|
||||||
header = if contentType == OpenAPI then openapiH else jsonH
|
header = if contentType == OpenAPI then openapiH else jsonH
|
||||||
body <- encodeFn <$> H.query schema accessibleTables
|
body <- encodeFn <$> H.query schema accessibleTables
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import Web.JWT (Secret, secret)
|
|||||||
data AppConfig = AppConfig {
|
data AppConfig = AppConfig {
|
||||||
configDatabase :: String
|
configDatabase :: String
|
||||||
, configAnonRole :: String
|
, configAnonRole :: String
|
||||||
|
, configProxyUri :: Maybe String
|
||||||
, configSchema :: String
|
, configSchema :: String
|
||||||
, configHost :: String
|
, configHost :: String
|
||||||
, configPort :: Int
|
, configPort :: Int
|
||||||
@@ -51,6 +52,7 @@ argParser :: Parser AppConfig
|
|||||||
argParser = AppConfig
|
argParser = AppConfig
|
||||||
<$> argument str (help "(REQUIRED) database connection string, e.g. postgres://user:pass@host:port/db" <> metavar "DB_URL")
|
<$> argument str (help "(REQUIRED) database connection string, e.g. postgres://user:pass@host:port/db" <> metavar "DB_URL")
|
||||||
<*> strOption (long "anonymous" <> short 'a' <> help "(REQUIRED) postgres role to use for non-authenticated requests" <> metavar "ROLE")
|
<*> strOption (long "anonymous" <> short 'a' <> help "(REQUIRED) postgres role to use for non-authenticated requests" <> metavar "ROLE")
|
||||||
|
<*> (optional . strOption) (long "proxy-uri" <> short 'x' <> help "proxy uri of the HTTP server" <> metavar "PROXY")
|
||||||
<*> strOption (long "schema" <> short 's' <> help "schema to use for API routes" <> metavar "NAME" <> value "public" <> showDefault)
|
<*> strOption (long "schema" <> short 's' <> help "schema to use for API routes" <> metavar "NAME" <> value "public" <> showDefault)
|
||||||
<*> strOption (long "host" <> short 'l' <> help "hostname or ip on which to run HTTP server" <> metavar "HOST" <> value "*4" <> showDefault)
|
<*> strOption (long "host" <> short 'l' <> help "hostname or ip on which to run HTTP server" <> metavar "HOST" <> value "*4" <> showDefault)
|
||||||
<*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault)
|
<*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault)
|
||||||
|
|||||||
+100
-11
@@ -2,24 +2,30 @@
|
|||||||
|
|
||||||
module PostgREST.OpenAPI (
|
module PostgREST.OpenAPI (
|
||||||
encodeOpenAPI
|
encodeOpenAPI
|
||||||
|
, isMalformedProxyUri
|
||||||
|
, pickProxy
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Data.Aeson (decode, encode)
|
import Data.Aeson (decode, encode)
|
||||||
import Data.ByteString.Lazy (ByteString)
|
import Data.ByteString.Lazy (ByteString)
|
||||||
import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList)
|
import Data.HashMap.Strict.InsOrd (InsOrdHashMap, fromList)
|
||||||
|
import Data.Maybe (isJust, isNothing, fromJust)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Data.Text (Text, unpack, pack, concat, intercalate)
|
import Data.Text (Text, unpack, pack, concat, intercalate, init, tail, toLower)
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
import Network.URI (parseURI, isAbsoluteURI,
|
||||||
|
URI (..), URIAuth (..))
|
||||||
|
|
||||||
import Prelude hiding (concat)
|
import Prelude hiding (concat, init, tail)
|
||||||
|
|
||||||
import Data.Swagger
|
import Data.Swagger
|
||||||
|
|
||||||
import PostgREST.ApiRequest (ContentType(..))
|
import PostgREST.ApiRequest (ContentType(..))
|
||||||
import PostgREST.Config (prettyVersion)
|
import PostgREST.Config (prettyVersion)
|
||||||
import PostgREST.QueryBuilder (operators)
|
import PostgREST.QueryBuilder (operators)
|
||||||
import PostgREST.Types (Table(..), Column(..))
|
import PostgREST.Types (Table(..), Column(..),
|
||||||
|
Proxy(..))
|
||||||
|
|
||||||
makeMimeList :: [ContentType] -> MimeList
|
makeMimeList :: [ContentType] -> MimeList
|
||||||
makeMimeList cs = MimeList $ map (fromString . show) cs
|
makeMimeList cs = MimeList $ map (fromString . show) cs
|
||||||
@@ -215,7 +221,7 @@ makeRootPathItem = ("/", p)
|
|||||||
makePathItems :: [(Table, [Column], [Text])] -> InsOrdHashMap FilePath PathItem
|
makePathItems :: [(Table, [Column], [Text])] -> InsOrdHashMap FilePath PathItem
|
||||||
makePathItems ti = fromList $ makeRootPathItem : map makePathItem ti
|
makePathItems ti = fromList $ makeRootPathItem : map makePathItem ti
|
||||||
|
|
||||||
escapeHostName :: String -> String
|
escapeHostName :: Text -> Text
|
||||||
escapeHostName "*" = "0.0.0.0"
|
escapeHostName "*" = "0.0.0.0"
|
||||||
escapeHostName "*4" = "0.0.0.0"
|
escapeHostName "*4" = "0.0.0.0"
|
||||||
escapeHostName "!4" = "0.0.0.0"
|
escapeHostName "!4" = "0.0.0.0"
|
||||||
@@ -223,10 +229,10 @@ escapeHostName "*6" = "0.0.0.0"
|
|||||||
escapeHostName "!6" = "0.0.0.0"
|
escapeHostName "!6" = "0.0.0.0"
|
||||||
escapeHostName h = h
|
escapeHostName h = h
|
||||||
|
|
||||||
postgrestSpec:: [(Table, [Column], [Text])] -> String -> Integer -> Swagger
|
postgrestSpec:: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> Swagger
|
||||||
postgrestSpec ti h p = (mempty :: Swagger)
|
postgrestSpec ti (s, h, p, b) = (mempty :: Swagger)
|
||||||
& basePath ?~ "/"
|
& basePath ?~ unpack b
|
||||||
& schemes ?~ [Http]
|
& schemes ?~ [s']
|
||||||
& info .~ ((mempty :: Info)
|
& info .~ ((mempty :: Info)
|
||||||
& version .~ pack prettyVersion
|
& version .~ pack prettyVersion
|
||||||
& title .~ "PostgREST API"
|
& title .~ "PostgREST API"
|
||||||
@@ -235,7 +241,90 @@ postgrestSpec ti h p = (mempty :: Swagger)
|
|||||||
& definitions .~ makeDefinitions ti
|
& definitions .~ makeDefinitions ti
|
||||||
& paths .~ makePathItems ti
|
& paths .~ makePathItems ti
|
||||||
where
|
where
|
||||||
h' = Just $ Host (escapeHostName h) (Just (fromInteger p))
|
s' = if s == "http" then Http else Https
|
||||||
|
h' = Just $ Host (unpack $ escapeHostName h) (Just (fromInteger p))
|
||||||
|
|
||||||
encodeOpenAPI :: [(Table, [Column], [Text])] -> String -> Integer -> ByteString
|
encodeOpenAPI :: [(Table, [Column], [Text])] -> (Text, Text, Integer, Text) -> ByteString
|
||||||
encodeOpenAPI ti h p = encode $ postgrestSpec ti h p
|
encodeOpenAPI ti uri = encode $ postgrestSpec ti uri
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Test whether a proxy uri is malformed or not.
|
||||||
|
A valid proxy uri should be an absolute uri without query and user info,
|
||||||
|
only http(s) schemes are valid, port number range is 1-65535.
|
||||||
|
|
||||||
|
For example
|
||||||
|
http://postgrest.com/openapi.json
|
||||||
|
https://postgrest.com:8080/openapi.json
|
||||||
|
-}
|
||||||
|
isMalformedProxyUri :: Maybe String -> Bool
|
||||||
|
isMalformedProxyUri Nothing = False
|
||||||
|
isMalformedProxyUri (Just uri)
|
||||||
|
| isAbsoluteURI uri = not $ isUriValid $ toURI uri
|
||||||
|
| otherwise = True
|
||||||
|
|
||||||
|
toURI :: String -> URI
|
||||||
|
toURI uri = fromJust $ parseURI uri
|
||||||
|
|
||||||
|
pickProxy :: Maybe String -> Maybe Proxy
|
||||||
|
pickProxy proxy
|
||||||
|
| isNothing proxy = Nothing
|
||||||
|
-- should never happen
|
||||||
|
-- since the request would have been rejected by the middleware if proxy uri
|
||||||
|
-- is malformed
|
||||||
|
| isMalformedProxyUri proxy = Nothing
|
||||||
|
| otherwise = Just Proxy {
|
||||||
|
proxyScheme = scheme
|
||||||
|
, proxyHost = host'
|
||||||
|
, proxyPort = port''
|
||||||
|
, proxyPath = path'
|
||||||
|
}
|
||||||
|
where
|
||||||
|
uri = toURI $ fromJust proxy
|
||||||
|
scheme = init $ toLower $ pack $ uriScheme uri
|
||||||
|
path URI {uriPath = ""} = "/"
|
||||||
|
path URI {uriPath = p} = p
|
||||||
|
path' = pack $ path uri
|
||||||
|
authority = fromJust $ uriAuthority uri
|
||||||
|
host' = pack $ uriRegName authority
|
||||||
|
port' = uriPort authority
|
||||||
|
port'' :: Integer
|
||||||
|
port'' = case (port', scheme) of
|
||||||
|
("", "http") -> 80
|
||||||
|
("", "https") -> 443
|
||||||
|
_ -> read $ unpack $ tail $ pack port'
|
||||||
|
|
||||||
|
isUriValid:: URI -> Bool
|
||||||
|
isUriValid = fAnd [isSchemeValid, isQueryValid, isAuthorityValid]
|
||||||
|
|
||||||
|
fAnd :: [a -> Bool] -> a -> Bool
|
||||||
|
fAnd fs x = all ($x) fs
|
||||||
|
|
||||||
|
isSchemeValid :: URI -> Bool
|
||||||
|
isSchemeValid URI {uriScheme = s}
|
||||||
|
| toLower (pack s) == "https:" = True
|
||||||
|
| toLower (pack s) == "http:" = True
|
||||||
|
| otherwise = False
|
||||||
|
|
||||||
|
isQueryValid :: URI -> Bool
|
||||||
|
isQueryValid URI {uriQuery = ""} = True
|
||||||
|
isQueryValid _ = False
|
||||||
|
|
||||||
|
isAuthorityValid :: URI -> Bool
|
||||||
|
isAuthorityValid URI {uriAuthority = a}
|
||||||
|
| isJust a = fAnd [isUserInfoValid, isHostValid, isPortValid] $ fromJust a
|
||||||
|
| otherwise = False
|
||||||
|
|
||||||
|
isUserInfoValid :: URIAuth -> Bool
|
||||||
|
isUserInfoValid URIAuth {uriUserInfo = ""} = True
|
||||||
|
isUserInfoValid _ = False
|
||||||
|
|
||||||
|
isHostValid :: URIAuth -> Bool
|
||||||
|
isHostValid URIAuth {uriRegName = ""} = False
|
||||||
|
isHostValid _ = True
|
||||||
|
|
||||||
|
isPortValid :: URIAuth -> Bool
|
||||||
|
isPortValid URIAuth {uriPort = ""} = True
|
||||||
|
isPortValid URIAuth {uriPort = (':':p)} =
|
||||||
|
let i :: Integer = read p in
|
||||||
|
i > 0 && i < 65536
|
||||||
|
isPortValid _ = False
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ data Payload = PayloadJSON UniformObjects
|
|||||||
| PayloadParseError BS.ByteString
|
| PayloadParseError BS.ByteString
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
data Proxy = Proxy {
|
||||||
|
proxyScheme :: Text
|
||||||
|
, proxyHost :: Text
|
||||||
|
, proxyPort :: Integer
|
||||||
|
, proxyPath :: Text
|
||||||
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
type Operator = Text
|
type Operator = Text
|
||||||
data FValue = VText Text | VForeignKey QualifiedIdentifier ForeignKey deriving (Show, Eq)
|
data FValue = VText Text | VForeignKey QualifiedIdentifier ForeignKey deriving (Show, Eq)
|
||||||
type FieldName = Text
|
type FieldName = Text
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ extra-deps:
|
|||||||
- hackage-security-0.5.2.1
|
- hackage-security-0.5.2.1
|
||||||
- swagger2-2.1
|
- swagger2-2.1
|
||||||
- hjsonpointer-0.3.0.1
|
- hjsonpointer-0.3.0.1
|
||||||
|
- network-uri-2.6.1.0
|
||||||
ghc-options:
|
ghc-options:
|
||||||
postgrest: -O2 -Werror -Wall -fwarn-identities
|
postgrest: -O2 -Werror -Wall -fwarn-identities
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module Feature.ProxySpec where
|
||||||
|
|
||||||
|
import Test.Hspec hiding (pendingWith)
|
||||||
|
|
||||||
|
import SpecHelper
|
||||||
|
|
||||||
|
import Network.Wai (Application)
|
||||||
|
|
||||||
|
spec :: SpecWith Application
|
||||||
|
spec =
|
||||||
|
describe "GET / with proxy" $
|
||||||
|
it "returns a valid openapi spec with proxy" $
|
||||||
|
validateOpenApiResponse [("Accept", "application/openapi+json")]
|
||||||
@@ -3,16 +3,12 @@ module Feature.StructureSpec where
|
|||||||
import Test.Hspec hiding (pendingWith)
|
import Test.Hspec hiding (pendingWith)
|
||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
|
import Network.HTTP.Types
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
|
||||||
import Network.HTTP.Types
|
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
import Network.Wai.Test (SResponse(simpleStatus, simpleHeaders, simpleBody))
|
import Network.Wai.Test (SResponse(simpleHeaders))
|
||||||
|
|
||||||
import Data.Maybe (fromJust)
|
|
||||||
import Data.Aeson (decode)
|
|
||||||
import qualified Data.JsonSchema.Draft4 as D4
|
|
||||||
|
|
||||||
spec :: SpecWith Application
|
spec :: SpecWith Application
|
||||||
spec = do
|
spec = do
|
||||||
@@ -65,27 +61,8 @@ spec = do
|
|||||||
] |]
|
] |]
|
||||||
{matchStatus = 200}
|
{matchStatus = 200}
|
||||||
|
|
||||||
it "returns a valid openapi spec" $ do
|
it "returns a valid openapi spec" $
|
||||||
r <- request methodGet "/" [("Accept", "application/openapi+json")] ""
|
validateOpenApiResponse [("Accept", "application/openapi+json")]
|
||||||
liftIO $
|
|
||||||
let respStatus = simpleStatus r in
|
|
||||||
respStatus `shouldSatisfy`
|
|
||||||
\s -> s == Status { statusCode = 200, statusMessage="OK" }
|
|
||||||
liftIO $
|
|
||||||
let respHeaders = simpleHeaders r in
|
|
||||||
respHeaders `shouldSatisfy`
|
|
||||||
\hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs
|
|
||||||
liftIO $
|
|
||||||
let respBody = simpleBody r
|
|
||||||
schema :: D4.Schema
|
|
||||||
schema = D4.emptySchema { D4._schemaRef = Just "openapi.json" }
|
|
||||||
schemaContext :: D4.SchemaWithURI D4.Schema
|
|
||||||
schemaContext = D4.SchemaWithURI
|
|
||||||
{ D4._swSchema = schema
|
|
||||||
, D4._swURI = Just "test/fixtures/openapi.json"
|
|
||||||
}
|
|
||||||
in
|
|
||||||
D4.fetchFilesystemAndValidate schemaContext ((fromJust . decode) respBody) `shouldReturn` Right ()
|
|
||||||
|
|
||||||
it "should respond to openapi request on none root path with 415" $
|
it "should respond to openapi request on none root path with 415" $
|
||||||
request methodGet "/none_root_path"
|
request methodGet "/none_root_path"
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import qualified Feature.QuerySpec
|
|||||||
import qualified Feature.RangeSpec
|
import qualified Feature.RangeSpec
|
||||||
import qualified Feature.StructureSpec
|
import qualified Feature.StructureSpec
|
||||||
import qualified Feature.UnicodeSpec
|
import qualified Feature.UnicodeSpec
|
||||||
|
import qualified Feature.ProxySpec
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
@@ -32,6 +33,7 @@ main = do
|
|||||||
let withApp = return $ postgrest testCfg refDbStructure pool
|
let withApp = return $ postgrest testCfg refDbStructure pool
|
||||||
ltdApp = return $ postgrest testLtdRowsCfg refDbStructure pool
|
ltdApp = return $ postgrest testLtdRowsCfg refDbStructure pool
|
||||||
unicodeApp = return $ postgrest testUnicodeCfg refDbStructure pool
|
unicodeApp = return $ postgrest testUnicodeCfg refDbStructure pool
|
||||||
|
proxyApp = return $ postgrest testProxyCfg refDbStructure pool
|
||||||
|
|
||||||
hspec $ do
|
hspec $ do
|
||||||
mapM_ (beforeAll_ resetDb . before withApp) specs
|
mapM_ (beforeAll_ resetDb . before withApp) specs
|
||||||
@@ -44,6 +46,10 @@ main = do
|
|||||||
beforeAll_ resetDb . before unicodeApp $
|
beforeAll_ resetDb . before unicodeApp $
|
||||||
describe "Feature.UnicodeSpec" Feature.UnicodeSpec.spec
|
describe "Feature.UnicodeSpec" Feature.UnicodeSpec.spec
|
||||||
|
|
||||||
|
-- this test runs with a proxy
|
||||||
|
beforeAll_ resetDb . before proxyApp $
|
||||||
|
describe "Feature.ProxySpec" Feature.ProxySpec.spec
|
||||||
|
|
||||||
where
|
where
|
||||||
specs = map (uncurry describe) [
|
specs = map (uncurry describe) [
|
||||||
("Feature.AuthSpec" , Feature.AuthSpec.spec)
|
("Feature.AuthSpec" , Feature.AuthSpec.spec)
|
||||||
|
|||||||
+40
-5
@@ -3,8 +3,6 @@ module SpecHelper where
|
|||||||
import Data.String.Conversions (cs)
|
import Data.String.Conversions (cs)
|
||||||
import Control.Monad (void)
|
import Control.Monad (void)
|
||||||
|
|
||||||
import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange,
|
|
||||||
hRange, hAuthorization, hAccept)
|
|
||||||
import Codec.Binary.Base64.String (encode)
|
import Codec.Binary.Base64.String (encode)
|
||||||
import Data.CaseInsensitive (CI(..))
|
import Data.CaseInsensitive (CI(..))
|
||||||
import Text.Regex.TDFA ((=~))
|
import Text.Regex.TDFA ((=~))
|
||||||
@@ -14,20 +12,57 @@ import Web.JWT (secret)
|
|||||||
|
|
||||||
import PostgREST.Config (AppConfig(..))
|
import PostgREST.Config (AppConfig(..))
|
||||||
|
|
||||||
|
import Test.Hspec hiding (pendingWith)
|
||||||
|
import Test.Hspec.Wai
|
||||||
|
|
||||||
|
import Network.HTTP.Types
|
||||||
|
import Network.Wai.Test (SResponse(simpleStatus, simpleHeaders, simpleBody))
|
||||||
|
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
|
import Data.Aeson (decode)
|
||||||
|
import qualified Data.JsonSchema.Draft4 as D4
|
||||||
|
|
||||||
|
validateOpenApiResponse :: [Header] -> WaiSession ()
|
||||||
|
validateOpenApiResponse headers = do
|
||||||
|
r <- request methodGet "/" headers ""
|
||||||
|
liftIO $
|
||||||
|
let respStatus = simpleStatus r in
|
||||||
|
respStatus `shouldSatisfy`
|
||||||
|
\s -> s == Status { statusCode = 200, statusMessage="OK" }
|
||||||
|
liftIO $
|
||||||
|
let respHeaders = simpleHeaders r in
|
||||||
|
respHeaders `shouldSatisfy`
|
||||||
|
\hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs
|
||||||
|
liftIO $
|
||||||
|
let respBody = simpleBody r
|
||||||
|
schema :: D4.Schema
|
||||||
|
schema = D4.emptySchema { D4._schemaRef = Just "openapi.json" }
|
||||||
|
schemaContext :: D4.SchemaWithURI D4.Schema
|
||||||
|
schemaContext = D4.SchemaWithURI
|
||||||
|
{ D4._swSchema = schema
|
||||||
|
, D4._swURI = Just "test/fixtures/openapi.json"
|
||||||
|
}
|
||||||
|
in
|
||||||
|
D4.fetchFilesystemAndValidate schemaContext ((fromJust . decode) respBody) `shouldReturn` Right ()
|
||||||
|
|
||||||
testDbConn :: String
|
testDbConn :: String
|
||||||
testDbConn = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_test"
|
testDbConn = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_test"
|
||||||
|
|
||||||
testCfg :: AppConfig
|
testCfg :: AppConfig
|
||||||
testCfg =
|
testCfg =
|
||||||
AppConfig testDbConn "postgrest_test_anonymous" "test" "localhost" 3000 (secret "safe") 10 Nothing True
|
AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (secret "safe") 10 Nothing True
|
||||||
|
|
||||||
testUnicodeCfg :: AppConfig
|
testUnicodeCfg :: AppConfig
|
||||||
testUnicodeCfg =
|
testUnicodeCfg =
|
||||||
AppConfig testDbConn "postgrest_test_anonymous" "تست" "localhost" 3000 (secret "safe") 10 Nothing True
|
AppConfig testDbConn "postgrest_test_anonymous" Nothing "تست" "localhost" 3000 (secret "safe") 10 Nothing True
|
||||||
|
|
||||||
testLtdRowsCfg :: AppConfig
|
testLtdRowsCfg :: AppConfig
|
||||||
testLtdRowsCfg =
|
testLtdRowsCfg =
|
||||||
AppConfig testDbConn "postgrest_test_anonymous" "test" "localhost" 3000 (secret "safe") 10 (Just 2) True
|
AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (secret "safe") 10 (Just 2) True
|
||||||
|
|
||||||
|
testProxyCfg :: AppConfig
|
||||||
|
testProxyCfg =
|
||||||
|
AppConfig testDbConn "postgrest_test_anonymous" (Just "https://postgrest.com/openapi.json") "test" "localhost" 3000 (secret "safe") 10 Nothing True
|
||||||
|
|
||||||
setupDb :: IO ()
|
setupDb :: IO ()
|
||||||
setupDb = do
|
setupDb = do
|
||||||
|
|||||||
Reference in New Issue
Block a user