test(spec): inline config into test suite

Previously, information about each test-suite was repeated in 3 separate
places:
- as a label and as implicit knowledge in the test-suite itself,
- as a comment in Main.hs, and
- as a configuration in SpecHelper.hs.

With this change, there will be a single source of truth in the test
suite itself. This will allow a single test-suite to easily test
multiple different configurations.
This commit is contained in:
Wolfgang Walther
2026-06-02 06:49:15 +00:00
parent 7803960cd1
commit 268ab00ed9
52 changed files with 280 additions and 439 deletions
+29 -9
View File
@@ -1,19 +1,39 @@
module Feature.Auth.AsymmetricJwtSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Text.Heredoc
import PostgREST.Config (AppConfig (..), parseSecret)
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "server started with asymmetric JWK" $
-- these tests will stop working 9999999999s after the UNIX EPOCH
spec :: SpecWithConfig
spec withConfig =
let
auth = authHeaderJWT "eyJhbGciOiJSUzI1NiJ9.eyJyb2xlIjogInBvc3RncmVzdF90ZXN0X2F1dGhvciJ9Cg.CBOYWDvqgAR0YYnZnyDGTQi6AJLc2Pds6_eV3YuBG6I36mj_h05eLhkEKNEDA5ZteMzCiY83P60rC_xtxVd7B6vo3BeF5uoanPS3rrbuHzKPwzsrgrD_CqvEuJ4n7Q9epkQiLsNkcexneENZDRqFjbwZx3DrXiCWwlK3Ytr5NAIGxmy0od-0xNpb2U1nXQyO_Q3mumWFViRt4tmFn_3goDHNKG3Ha_AzImfUNvHnWL78kAc4rbn15vLtWXD8PwtSnZaB4lY4V6RfsaW937srQsmRetvytM1i_bHBnjkjQLAqGbXPyItjtlXPs0uGNBadE8-wgkLtfmSCC4v2DjUthw"
jwk = encodeUtf8 [str|{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}|]
jwks = encodeUtf8 [str|{"keys": [{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}]}|]
in
describe "server started with asymmetric JWK" $ do
-- this test will stop working 9999999999s after the UNIX EPOCH
it "succeeds with jwt token signed with an asymmetric key" $ do
let auth = authHeaderJWT "eyJhbGciOiJSUzI1NiJ9.eyJyb2xlIjogInBvc3RncmVzdF90ZXN0X2F1dGhvciJ9Cg.CBOYWDvqgAR0YYnZnyDGTQi6AJLc2Pds6_eV3YuBG6I36mj_h05eLhkEKNEDA5ZteMzCiY83P60rC_xtxVd7B6vo3BeF5uoanPS3rrbuHzKPwzsrgrD_CqvEuJ4n7Q9epkQiLsNkcexneENZDRqFjbwZx3DrXiCWwlK3Ytr5NAIGxmy0od-0xNpb2U1nXQyO_Q3mumWFViRt4tmFn_3goDHNKG3Ha_AzImfUNvHnWL78kAc4rbn15vLtWXD8PwtSnZaB4lY4V6RfsaW937srQsmRetvytM1i_bHBnjkjQLAqGbXPyItjtlXPs0uGNBadE8-wgkLtfmSCC4v2DjUthw"
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 200
context "secret provided as JWK" $ withConfig (
baseCfg {
configJwtSecret = Just jwk
, configJWKS = rightToMaybe $ parseSecret jwk
}
) $ it "succeeds with jwt token signed with an asymmetric key" $
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 200
context "secret provided as JWKSet" $ withConfig (
baseCfg {
configJwtSecret = Just jwks
, configJWKS = rightToMaybe $ parseSecret jwks
}
) $ it "succeeds with jwt token signed with an asymmetric key" $
request methodGet "/authors_only" [auth] ""
`shouldRespondWith` 200
@@ -1,7 +1,5 @@
module Feature.Auth.AudienceJwtSecretSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Protolude hiding (get)
import SpecHelper
@@ -9,8 +7,16 @@ import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
spec :: SpecWith ((), Application)
spec = describe "test handling of aud claims in JWT when the jwt-aud config is set" $ do
import PostgREST.Config (AppConfig (..), parseSecret)
spec :: SpecWithConfig
spec withConfig = withConfig (
baseCfg {
configJwtSecret = Just generateSecret
, configJwtAudience = Just "youraudience"
, configJWKS = rightToMaybe $ parseSecret generateSecret
}
) $ describe "test handling of aud claims in JWT when the jwt-aud config is set" $ do
context "when the audience claim is a string" $ do
-- this test will stop working 9999999999s after the UNIX EPOCH
@@ -147,8 +153,8 @@ spec = describe "test handling of aud claims in JWT when the jwt-aud config is s
it "succeeds without a JWT" $
get "/has_count_column" `shouldRespondWith` 200
disabledSpec :: SpecWith ((), Application)
disabledSpec = describe "test handling of aud claims in JWT when the jwt-aud config is not set" $ do
disabledSpec :: SpecWithConfig
disabledSpec withConfig = withConfig baseCfg $ describe "test handling of aud claims in JWT when the jwt-aud config is not set" $ do
context "when the audience claim is a string" $ do
it "ignores the audience claim and suceeds" $ do
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Auth.AuthSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "authorization" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "authorization" $ do
let single = ("Accept","application/vnd.pgrst.object+json")
it "denies access to tables that anonymous does not own" $
@@ -1,16 +1,21 @@
module Feature.Auth.BinaryJwtSecretSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import PostgREST.Config (AppConfig (..), parseSecret)
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "server started with binary JWT secret" $
spec :: SpecWithConfig
spec withConfig = withConfig (
baseCfg {
configJwtSecret = Just generateSecret
, configJWKS = rightToMaybe $ parseSecret generateSecret
}
) $ describe "server started with binary JWT secret" $
-- this test will stop working 9999999999s after the UNIX EPOCH
it "succeeds with jwt token encoded with a binary secret" $ do
+4 -4
View File
@@ -1,17 +1,17 @@
module Feature.Auth.NoAnonSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "server started without anonymous role" $ do
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbAnonRole = Nothing }) $ describe "server started without anonymous role" $ do
it "behaves normally on attempted auth" $ do
-- token body: { "role": "postgrest_test_author" }
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
+9 -4
View File
@@ -1,17 +1,22 @@
module Feature.Auth.NoJwtSecretSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "server started without JWT secret" $ do
spec :: SpecWithConfig
spec withConfig = withConfig (
baseCfg {
configJwtSecret = Nothing
, configJWKS = Nothing
}
) $ describe "server started without JWT secret" $ do
it "responds with error on attempted auth" $ do
-- token body: { "role": "postgrest_test_author" }
+4 -4
View File
@@ -5,7 +5,6 @@
module Feature.ConcurrentSpec where
import Control.Concurrent.Async (mapConcurrently)
import Network.Wai (Application)
import Control.Monad.Base
import Control.Monad.Trans.Control
@@ -16,10 +15,11 @@ import Test.Hspec.Wai
import Test.Hspec.Wai.Internal
import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "Querying in parallel" $
it "should not raise 'transaction in progress' error" $
raceTest 10 $
+3 -4
View File
@@ -1,15 +1,14 @@
module Feature.CorsSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "CORS" $ do
it "replies naively and permissively to preflight request" $
request methodOptions "/"
+4 -3
View File
@@ -1,16 +1,17 @@
module Feature.ExtraSearchPathSpec where
import Network.HTTP.Types
import Network.Wai (Application)
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "extra search path" $ do
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbExtraSearchPath = ["public", "extensions", "EXTRA \"@/\\#~_-"] }) $ describe "extra search path" $ do
it "finds the ltree <@ operator on the public schema" $
request methodGet "/ltree_sample?path=cd.Top.Science.Astronomy" [] ""
+3 -4
View File
@@ -1,15 +1,14 @@
module Feature.NoSuperuserSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "No Superuser" $ do
it "proves that the authenticator role is not a superuser" $ do
request methodGet "/rpc/is_superuser"
+7 -4
View File
@@ -1,15 +1,18 @@
module Feature.ObservabilitySpec where
import Network.Wai (Application)
import Data.CaseInsensitive (mk)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Protolude
import PostgREST.Config (AppConfig (..))
spec :: SpecWith ((), Application)
spec =
import Protolude
import SpecHelper
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configServerTraceHeader = Just $ mk "X-Request-Id" }) $
describe "Observability" $ do
it "includes the server trace header on the response" $ do
request methodHead "/"
@@ -1,16 +1,18 @@
module Feature.OpenApi.DisabledOpenApiSpec where
import Network.HTTP.Types
import Network.Wai (Application)
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude
import PostgREST.Config (AppConfig (..), OpenAPIMode (..))
spec :: SpecWith ((), Application)
spec =
import Protolude
import SpecHelper
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configOpenApiMode = OADisabled }) $
describe "Disabled OpenApi" $ do
it "responds with 404" $
request methodGet "/"
@@ -4,19 +4,26 @@ import Control.Lens ((^?))
import Data.Aeson.Lens
import Data.Aeson.QQ
import Data.List.NonEmpty (fromList)
import Network.HTTP.Types
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import PostgREST.Config (AppConfig (..), OpenAPIMode (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "OpenAPI Ignore Privileges" $ do
spec :: SpecWithConfig
spec withConfig = withConfig (
baseCfg {
configOpenApiMode = OAIgnorePriv
, configDbSchemas = fromList ["test", "v1"]
}
) $ describe "OpenAPI Ignore Privileges" $ do
it "root path returns a valid openapi spec" $ do
validateOpenApiResponse [("Accept", "application/openapi+json")]
request methodHead "/"
+2 -3
View File
@@ -2,7 +2,6 @@ module Feature.OpenApi.OpenApiSpec where
import Control.Lens ((^?))
import Data.Aeson.Types (Value (..))
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Data.Aeson.Lens
@@ -15,8 +14,8 @@ import PostgREST.Version (docsVersion)
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "OpenAPI" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
it "root path returns a valid openapi spec" $ do
validateOpenApiResponse [("Accept", "application/openapi+json")]
request methodHead "/"
+5 -4
View File
@@ -1,13 +1,14 @@
module Feature.OpenApi.ProxySpec where
import Network.Wai (Application)
import Test.Hspec hiding (pendingWith)
import Test.Hspec hiding (pendingWith)
import PostgREST.Config (AppConfig (..))
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configOpenApiServerProxyUri = Just "https://postgrest.com/openapi.json" }) $
describe "GET / with proxy" $
it "returns a valid openapi spec with proxy" $
validateOpenApiResponse [("Accept", "application/openapi+json")]
+7 -4
View File
@@ -1,16 +1,19 @@
module Feature.OpenApi.RootSpec where
import Network.HTTP.Types
import Network.Wai (Application)
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import PostgREST.Config (AppConfig (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
spec :: SpecWith ((), Application)
spec =
import Protolude hiding (get)
import SpecHelper
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root" }) $
describe "root spec function" $ do
it "accepts application/openapi+json" $ do
request methodGet "/"
@@ -5,16 +5,18 @@ import Control.Lens ((^?))
import Data.Aeson.Lens
import Data.Aeson.QQ
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Protolude hiding (get)
import PostgREST.Config (AppConfig (..))
spec :: SpecWith ((), Application)
spec =
import Protolude hiding (get)
import SpecHelper
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configOpenApiSecurityActive = True }) $
describe "Security active" $
it "includes security and security definitions" $ do
r <- simpleBody <$> get "/"
+2 -3
View File
@@ -1,6 +1,5 @@
module Feature.OptionsSpec where
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Network.HTTP.Types
@@ -10,8 +9,8 @@ import Test.Hspec.Wai
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "Allow header" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "Allow header" $ do
context "a table" $ do
it "includes read/write methods for writeable table" $ do
r <- request methodOptions "/items" [] ""
@@ -1,16 +1,16 @@
module Feature.Query.AggregateFunctionsSpec where
import Network.Wai (Application)
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
allowed :: SpecWith ((), Application)
allowed =
allowed :: SpecWithConfig
allowed withConfig = withConfig (baseCfg { configDbAggregates = True }) $
describe "aggregate functions" $ do
context "performing a count without specifying a field" $ do
it "returns the count of all rows when no other fields are selected" $
@@ -306,8 +306,8 @@ allowed =
{ matchStatus = 400
, matchHeaders = [matchContentTypeJson] }
disallowed :: SpecWith ((), Application)
disallowed =
disallowed :: SpecWithConfig
disallowed withConfig = withConfig baseCfg $
describe "attempting to use an aggregate when aggregate functions are disallowed" $ do
it "prevents the use of aggregates" $
get "/project_invoices?select=invoice_total.sum()" `shouldRespondWith`
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.AndOrParamsSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "and/or params used for complex boolean logic" $ do
context "used with GET" $ do
context "or param" $ do
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.ComputedRelsSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "computed relationships" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "computed relationships" $ do
it "can define a many-to-one relationship with SETOF and ROWS 1 and embed" $
get "/videogames?select=name,designer:computed_designers(name)"
`shouldRespondWith`
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.CustomMediaSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Network.Wai.Test (SResponse (simpleBody, simpleHeaders, simpleStatus))
import Test.Hspec
@@ -12,8 +10,8 @@ import Text.Heredoc (str)
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "custom media types" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "custom media types" $ do
context "for tables with aggregate" $ do
it "can query if there's an aggregate defined for the table" $ do
r <- request methodGet "/lines" (acceptHdrs "application/vnd.twkb") ""
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.DeleteSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "Deleting" $ do
context "existing record" $ do
it "succeeds with 204 and deletion count" $
@@ -1,7 +1,5 @@
module Feature.Query.EmbedDisambiguationSpec where
import Network.Wai (Application)
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
@@ -9,8 +7,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "resource embedding disambiguation" $ do
context "ambiguous requests that give 300 Multiple Choices" $ do
it "errs when there are o2m and m2m cardinalities to the target table" $
@@ -1,7 +1,6 @@
module Feature.Query.EmbedInnerJoinSpec where
import Network.HTTP.Types
import Network.Wai (Application)
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +9,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "Embedding with an inner join" $ do
context "many-to-one relationships" $ do
it "ignores null embeddings while the default left join doesn't" $ do
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.ErrorSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
pgErrorCodeMapping :: SpecWith ((), Application)
pgErrorCodeMapping = do
pgErrorCodeMapping :: SpecWithConfig
pgErrorCodeMapping withConfig = withConfig baseCfg $ do
describe "PostreSQL error code mappings" $ do
it "should return 500 for cardinality_violation" $
get "/bad_subquery" `shouldRespondWith` 500
+2 -3
View File
@@ -1,7 +1,6 @@
module Feature.Query.InsertSpec where
import Data.List (lookup)
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleHeaders))
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai.Matcher (bodyEquals)
@@ -14,8 +13,8 @@ import Text.Heredoc
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ do
describe "Posting new record" $ do
context "disparate json types" $ do
it "accepts disparate json types" $ do
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.JsonOperatorSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "json and jsonb operators" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "json and jsonb operators" $ do
context "Shaping response with select parameter" $ do
it "obtains a json subfield one level with casting" $
get "/complex_items?id=eq.1&select=settings->>foo::json" `shouldRespondWith`
@@ -1,22 +1,24 @@
module Feature.Query.MultipleSchemaSpec where
import Control.Lens ((^?))
import Control.Lens ((^?))
import Data.Aeson.Lens
import Data.Aeson.QQ
import Data.List.NonEmpty (fromList)
import Network.HTTP.Types
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleHeaders), simpleBody)
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbSchemas = fromList ["v1", "v2", "SPECIAL \"@/\\#~_-"] }) $ describe "PostGIS features" $
describe "multiple schemas in single instance" $ do
context "Reading tables on different schemas" $ do
it "succeeds in reading table from default schema v1 if no schema is selected via header" $
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.NullsStripSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "Stripping null values from JSON response" $ do
let arrayStrip = ("Accept", "application/vnd.pgrst.array+json;nulls=stripped")
let singularStrip = ("Accept", "application/vnd.pgrst.object+json;nulls=stripped")
+9 -7
View File
@@ -1,16 +1,18 @@
module Feature.Query.PgSafeUpdateSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude hiding (get, put)
import PostgREST.Config (AppConfig (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
spec :: SpecWith ((), Application)
spec =
import Protolude hiding (get, put)
import SpecHelper
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbPreRequest = Just $ QualifiedIdentifier "test" "load_safeupdate" }) $
describe "Enabling pg-safeupdate" $ do
context "Full table update" $ do
it "does not update and throws error if no condition is present" $
@@ -48,8 +50,8 @@ spec =
`shouldRespondWith`
204
disabledSpec :: SpecWith ((), Application)
disabledSpec =
disabledSpec :: SpecWithConfig
disabledSpec withConfig = withConfig baseCfg $
describe "Disabling pg-safeupdate" $ do
context "Full table update" $ do
it "works if no condition is present" $
+6 -5
View File
@@ -3,7 +3,6 @@
module Feature.Query.PlanSpec where
import Control.Lens ((^?))
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Data.Aeson.Lens
@@ -15,11 +14,13 @@ import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = do
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbPlanEnabled = True }) $ do
describe "read table/view plan" $ do
it "outputs the total cost for a single filter on a table" $ do
r <- request methodGet "/projects?id=in.(1,2,3)"
@@ -529,8 +530,8 @@ spec = do
totalCost `shouldSatisfy` (> 67.0)
aggregateQty `shouldSatisfy` (> 1)
disabledSpec :: SpecWith ((), Application)
disabledSpec =
disabledSpec :: SpecWithConfig
disabledSpec withConfig = withConfig baseCfg $
it "doesn't work if db-plan-enabled=false(the default)" $ do
request methodGet "/projects?id=in.(1,2,3)"
(acceptHdrs "application/vnd.pgrst.plan") ""
+4 -4
View File
@@ -1,17 +1,17 @@
module Feature.Query.PostGISSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "PostGIS features" $
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbExtraSearchPath = ["public", "extensions", "EXTRA \"@/\\#~_-"] }) $ describe "PostGIS features" $
context "GeoJSON output" $ do
it "works for a table that has a geometry column" $
request methodGet "/shops"
@@ -1,7 +1,5 @@
module Feature.Query.Preferences.HandlingSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "test Prefer: handling" $ do
context "check behaviour of Prefer: handling=strict" $ do
it "throws error when handling=strict and invalid prefs are given" $
@@ -1,17 +1,15 @@
module Feature.Query.Preferences.MaxAffectedSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
describe "test Prefer: max-affected" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "test Prefer: max-affected" $ do
context "test Prefer: max-affected with handling=strict" $ do
it "should fail if items deleted more than 10" $
request methodDelete "/items?id=lt.15"
@@ -1,18 +1,17 @@
module Feature.Query.Preferences.TimezoneSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
enabledSpec :: SpecWith ((), Application)
enabledSpec =
describe "test Prefer: timezone with db-timezone-enabled is true" $ do
enabledSpec :: SpecWithConfig
enabledSpec withConfig = withConfig baseCfg $ describe "test Prefer: timezone with db-timezone-enabled is true" $ do
context "test Prefer: timezone=America/Los_Angeles" $ do
it "should change timezone with handling=strict" $
request methodGet "/timestamps"
@@ -62,8 +61,8 @@ enabledSpec =
, "Preference-Applied" <:> "handling=lenient"]}
disabledSpec :: SpecWith ((), Application)
disabledSpec =
disabledSpec :: SpecWithConfig
disabledSpec withConfig = withConfig (baseCfg { configDbTimezoneEnabled = False }) $
describe "test Prefer: timezone with db-timezone-enabled is false" $ do
context "test Prefer: timezone=America/Los_Angeles when timezone is disabled" $ do
it "should throw error with handling=strict" $
+4 -4
View File
@@ -1,17 +1,17 @@
module Feature.Query.QueryLimitedSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbMaxRows = Just 2 }) $
describe "Requesting many items with server limits(max-rows) enabled" $ do
it "restricts results" $
get "/items?order=id"
+2 -3
View File
@@ -1,6 +1,5 @@
module Feature.Query.QuerySpec where
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleHeaders))
import Network.HTTP.Types
@@ -11,8 +10,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ do
describe "Querying a table with a column called count" $
it "should not confuse count column with pg_catalog.count aggregate" $
+2 -3
View File
@@ -1,6 +1,5 @@
module Feature.Query.RangeSpec where
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleHeaders, simpleStatus))
import Network.HTTP.Types
@@ -11,8 +10,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ do
describe "GET /rpc/getitemrange" $ do
context "without range headers" $ do
context "with response under server size limit" $
@@ -1,17 +1,15 @@
module Feature.Query.RawOutputTypesSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude
import SpecHelper (acceptHdrs)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "When raw-media-types config variable is missing or left empty" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "When raw-media-types config variable is missing or left empty" $ do
let firefoxAcceptHdrs = acceptHdrs "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
chromeAcceptHdrs = acceptHdrs "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
it "responds json to a GET request with Firefox Accept headers" $
@@ -1,7 +1,5 @@
module Feature.Query.RelatedQueriesSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -10,8 +8,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "related queries" $ do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ describe "related queries" $ do
context "related orders" $ do
it "works on a many-to-one relationship" $ do
get "/projects?select=id,clients(name)&order=clients(name).nullsfirst" `shouldRespondWith`
+2 -3
View File
@@ -2,7 +2,6 @@ module Feature.Query.RpcSpec where
import qualified Data.ByteString.Lazy as BL (empty)
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleBody, simpleHeaders, simpleStatus))
import Network.HTTP.Types
@@ -16,8 +15,8 @@ import PostgREST.Config.PgVersion (PgVersion, pgVersion180)
import Protolude hiding (get)
import SpecHelper
spec :: PgVersion -> SpecWith ((), Application)
spec actualPgVersion =
spec :: PgVersion -> SpecWithConfig
spec actualPgVersion withConfig = withConfig baseCfg $
describe "remote procedure call" $ do
context "a proc that returns a set" $ do
context "returns paginated results" $ do
+4 -4
View File
@@ -1,17 +1,17 @@
module Feature.Query.ServerTimingSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbPlanEnabled = True }) $
describe "Show Duration on Server-Timing header" $ do
context "responds with Server-Timing header" $ do
+2 -3
View File
@@ -1,6 +1,5 @@
module Feature.Query.SingularSpec where
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Network.HTTP.Types
@@ -11,8 +10,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "Requesting singular json object" $ do
let singular = ("Accept", "application/vnd.pgrst.object+json")
+2 -4
View File
@@ -1,7 +1,5 @@
module Feature.Query.SpreadQueriesSpec where
import Network.Wai (Application)
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
@@ -9,8 +7,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "spread embeds" $ do
it "works on a many-to-one relationship" $ do
get "/projects?select=id,...clients(client_name:name)" `shouldRespondWith`
+5 -4
View File
@@ -1,17 +1,18 @@
module Feature.Query.UnicodeSpec where
import Network.Wai (Application)
import Data.List.NonEmpty (fromList)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbSchemas = fromList ["تست"] }) $
describe "Reading and writing to unicode schema and table names" $
it "Can read and write values" $ do
get "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
+3 -4
View File
@@ -1,7 +1,6 @@
module Feature.Query.UpdateSpec where
import Network.Wai (Application)
import Test.Hspec hiding (pendingWith)
import Test.Hspec hiding (pendingWith)
import Network.HTTP.Types
import Test.Hspec.Wai
@@ -10,8 +9,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = do
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $ do
describe "Patching record" $ do
context "to unknown uri" $
it "indicates no table found by returning 404" $
+2 -4
View File
@@ -3,8 +3,6 @@
-- - Upsert/IgnoreDuplicates.hs
module Feature.Query.UpsertSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
@@ -13,8 +11,8 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get, put)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig baseCfg $
describe "UPSERT" $ do
context "with POST" $ do
context "when Prefer: resolution=merge-duplicates is specified" $ do
+18 -8
View File
@@ -1,12 +1,12 @@
module Feature.RollbackSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import Protolude hiding (get)
import SpecHelper
@@ -211,8 +211,8 @@ shouldNotPersistMutations reqHeaders respHeaders = do
`shouldRespondWith`
[json|[{"id":1}]|]
allowed :: SpecWith ((), Application)
allowed = describe "tx-allow-override = true" $ do
allowed :: SpecWithConfig
allowed withConfig = withConfig baseCfg $ describe "tx-allow-override = true" $ do
describe "without Prefer tx" $ do
preferDefault `shouldRespondToReads` withoutPreferenceApplied
preferDefault `shouldNotPersistMutations` withoutPreferenceApplied
@@ -232,8 +232,13 @@ allowed = describe "tx-allow-override = true" $ do
-- because they return before the end of the transaction.
preferRollback `shouldRaiseExceptions` withoutPreferenceApplied
disallowed :: SpecWith ((), Application)
disallowed = describe "tx-rollback-all = false, tx-allow-override = false" $ do
disallowed :: SpecWithConfig
disallowed withConfig = withConfig (
baseCfg {
configDbTxAllowOverride = False
, configDbTxRollbackAll = False
}
) $ describe "tx-rollback-all = false, tx-allow-override = false" $ do
describe "without Prefer tx" $ do
preferDefault `shouldRespondToReads` withoutPreferenceApplied
preferDefault `shouldPersistMutations` withoutPreferenceApplied
@@ -250,8 +255,13 @@ disallowed = describe "tx-rollback-all = false, tx-allow-override = false" $ do
preferRollback `shouldRaiseExceptions` withoutPreferenceApplied
forced :: SpecWith ((), Application)
forced = describe "tx-rollback-all = true, tx-allow-override = false" $ do
forced :: SpecWithConfig
forced withConfig = withConfig (
baseCfg {
configDbTxAllowOverride = False
, configDbTxRollbackAll = True
}
) $ describe "tx-rollback-all = true, tx-allow-override = false" $ do
describe "without Prefer tx" $ do
preferDefault `shouldRespondToReads` withoutPreferenceApplied
preferDefault `shouldNotPersistMutations` withoutPreferenceApplied
+5 -4
View File
@@ -1,17 +1,18 @@
module Feature.RpcPreRequestGucsSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import PostgREST.Config (AppConfig (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import Protolude hiding (get, put)
import SpecHelper
spec :: SpecWith ((), Application)
spec =
spec :: SpecWithConfig
spec withConfig = withConfig (baseCfg { configDbPreRequest = Just $ QualifiedIdentifier mempty "custom_headers" }) $
describe "GUC headers on all methods via pre-request" $ do
it "succeeds setting the headers on POST" $
post "/items"
+34 -130
View File
@@ -79,15 +79,15 @@ main = do
, P.acquisitionTimeout 10
, P.agingTimeout 60
, P.idlenessTimeout 60
, P.staticConnectionSettings $ toConnectionSettings identity testCfg
, P.staticConnectionSettings $ toConnectionSettings identity baseCfg
]
actualPgVersion <- either (panic . show) id <$> P.use pool queryPgVersion
-- cached schema cache so most tests run fast
baseSchemaCache <- loadSCache pool testCfg
baseSchemaCache <- loadSCache pool baseCfg
loggerState <- Logger.init
metricsState <- Metrics.init (configDbPoolSize testCfg)
metricsState <- Metrics.init (configDbPoolSize baseCfg)
let
initApp sCache config = do
@@ -104,42 +104,29 @@ main = do
customSchemaCache <- loadSCache pool config
initApp customSchemaCache config
let withApp = app testCfg
maxRowsApp = app testMaxRowsCfg
disabledOpenApi = app testDisabledOpenApiCfg
securityOpenApi = app testSecurityOpenApiCfg
proxyApp = app testProxyCfg
noAnonApp = app testCfgNoAnon
noJwtSecretApp = app testCfgNoJwtSecret
binaryJwtApp = app testCfgBinaryJWT
audJwtApp = app testCfgAudienceJWT
asymJwkApp = app testCfgAsymJWK
asymJwkSetApp = app testCfgAsymJWKSet
rootSpecApp = app testCfgRootSpec
responseHeadersApp = app testCfgResponseHeaders
disallowRollbackApp = app testCfgDisallowRollback
forceRollbackApp = app testCfgForceRollback
planEnabledApp = app testPlanEnabledCfg
pgSafeUpdateApp = app testPgSafeUpdateEnabledCfg
obsApp = app testObservabilityCfg
serverTiming = app testCfgServerTiming
aggregatesEnabled = app testCfgAggregatesEnabled
withConfig config = before (app config)
withConfigDbs config = before (appDbs config)
describeWithConfig label spec = describe label $ spec withConfig
extraSearchPathApp = appDbs testCfgExtraSearchPath
unicodeApp = appDbs testUnicodeCfg
multipleSchemaApp = appDbs testMultipleSchemaCfg
ignorePrivOpenApi = appDbs testIgnorePrivOpenApiCfg
timezoneDisabled = appDbs testCfgTimezoneDisabled
let specs = uncurry describe <$> [
("Feature.Auth.AudienceJwtSecretSpec" , Feature.Auth.AudienceJwtSecretSpec.disabledSpec)
let specs = uncurry describeWithConfig <$> [
("Feature.Auth.AsymmetricJwtSpec" , Feature.Auth.AsymmetricJwtSpec.spec)
, ("Feature.Auth.AudienceJwtSecretSpec" , Feature.Auth.AudienceJwtSecretSpec.disabledSpec)
, ("Feature.Auth.AudienceJwtSecretSpec" , Feature.Auth.AudienceJwtSecretSpec.spec)
, ("Feature.Auth.AuthSpec" , Feature.Auth.AuthSpec.spec)
, ("Feature.Auth.BinaryJwtSecretSpec" , Feature.Auth.BinaryJwtSecretSpec.spec)
, ("Feature.Auth.NoAnonSpec" , Feature.Auth.NoAnonSpec.spec)
, ("Feature.Auth.NoJwtSecretSpec" , Feature.Auth.NoJwtSecretSpec.spec)
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
, ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec)
, ("Feature.ObservabilitySpec" , Feature.ObservabilitySpec.spec)
, ("Feature.OpenApi.DisabledOpenApiSpec" , Feature.OpenApi.DisabledOpenApiSpec.spec)
, ("Feature.OpenApi.OpenApiSpec" , Feature.OpenApi.OpenApiSpec.spec)
, ("Feature.OpenApi.ProxySpec" , Feature.OpenApi.ProxySpec.spec)
, ("Feature.OpenApi.RootSpec" , Feature.OpenApi.RootSpec.spec)
, ("Feature.OpenApi.SecurityOpenApiSpec" , Feature.OpenApi.SecurityOpenApiSpec.spec)
, ("Feature.OptionsSpec" , Feature.OptionsSpec.spec)
, ("Feature.Query.AggregateFunctionsSpec.allowed" , Feature.Query.AggregateFunctionsSpec.allowed)
, ("Feature.Query.AggregateFunctionsSpec.disallowed" , Feature.Query.AggregateFunctionsSpec.disallowed)
, ("Feature.Query.AndOrParamsSpec" , Feature.Query.AndOrParamsSpec.spec)
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
@@ -153,126 +140,43 @@ main = do
, ("Feature.Query.NullsStripSpec" , Feature.Query.NullsStripSpec.spec)
, ("Feature.Query.PgSafeUpdateSpec.disabledSpec" , Feature.Query.PgSafeUpdateSpec.disabledSpec)
, ("Feature.Query.PlanSpec.disabledSpec" , Feature.Query.PlanSpec.disabledSpec)
, ("Feature.Query.PlanSpec.spec" , Feature.Query.PlanSpec.spec)
, ("Feature.Query.Preferences.HandlingSpec" , Feature.Query.Preferences.HandlingSpec.spec)
, ("Feature.Query.Preferences.MaxAffectedSpec" , Feature.Query.Preferences.MaxAffectedSpec.spec)
, ("Feature.Query.Preferences.TimezoneSpec.enabledSpec", Feature.Query.Preferences.TimezoneSpec.enabledSpec)
, ("Feature.Query.QueryLimitedSpec" , Feature.Query.QueryLimitedSpec.spec)
, ("Feature.Query.QuerySpec" , Feature.Query.QuerySpec.spec)
, ("Feature.Query.RangeSpec" , Feature.Query.RangeSpec.spec)
, ("Feature.Query.RawOutputTypesSpec" , Feature.Query.RawOutputTypesSpec.spec)
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
, ("Feature.Query.RpcSpec" , Feature.Query.RpcSpec.spec actualPgVersion)
, ("Feature.Query.ServerTimingSpec" , Feature.Query.ServerTimingSpec.spec)
, ("Feature.Query.SingularSpec" , Feature.Query.SingularSpec.spec)
, ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec)
, ("Feature.Query.UpdateSpec" , Feature.Query.UpdateSpec.spec)
, ("Feature.Query.UpsertSpec" , Feature.Query.UpsertSpec.spec)
, ("Feature.RpcPreRequestGucsSpec" , Feature.RpcPreRequestGucsSpec.spec)
]
hspec $ do
mapM_ (parallel . before withApp) specs
-- this test runs with a different server flag
parallel $ before maxRowsApp $
describe "Feature.Query.QueryLimitedSpec" Feature.Query.QueryLimitedSpec.spec
-- this test runs with a different schema
parallel $ before unicodeApp $
describe "Feature.Query.UnicodeSpec" Feature.Query.UnicodeSpec.spec
-- this test runs with openapi-mode set to disabled
parallel $ before disabledOpenApi $
describe "Feature.DisabledOpenApiSpec" Feature.OpenApi.DisabledOpenApiSpec.spec
-- this test runs with openapi-mode set to ignore-acl
parallel $ before ignorePrivOpenApi $
describe "Feature.OpenApi.IgnorePrivOpenApiSpec" Feature.OpenApi.IgnorePrivOpenApiSpec.spec
-- this test runs with a proxy
parallel $ before proxyApp $
describe "Feature.OpenApi.ProxySpec" Feature.OpenApi.ProxySpec.spec
-- this test runs with openapi-security-active set to true
parallel $ before securityOpenApi $
describe "Feature.OpenApi.SecurityOpenApiSpec" Feature.OpenApi.SecurityOpenApiSpec.spec
-- this test runs without an anonymous role
parallel $ before noAnonApp $
describe "Feature.Auth.NoAnonSpec" Feature.Auth.NoAnonSpec.spec
-- this test runs without a JWT secret
parallel $ before noJwtSecretApp $
describe "Feature.Auth.NoJwtSecretSpec" Feature.Auth.NoJwtSecretSpec.spec
-- this test runs with a binary JWT secret
parallel $ before binaryJwtApp $
describe "Feature.Auth.BinaryJwtSecretSpec" Feature.Auth.BinaryJwtSecretSpec.spec
-- this test runs with a binary JWT secret and an audience claim
parallel $ before audJwtApp $
describe "Feature.Auth.AudienceJwtSecretSpec" Feature.Auth.AudienceJwtSecretSpec.spec
-- this test runs with asymmetric JWK
parallel $ before asymJwkApp $
describe "Feature.Auth.AsymmetricJwtSpec" Feature.Auth.AsymmetricJwtSpec.spec
-- this test runs with asymmetric JWKSet
parallel $ before asymJwkSetApp $
describe "Feature.Auth.AsymmetricJwtSpec" Feature.Auth.AsymmetricJwtSpec.spec
-- this test runs with an extra search path
parallel $ before extraSearchPathApp $ do
describe "Feature.ExtraSearchPathSpec" Feature.ExtraSearchPathSpec.spec
describe "Feature.Query.PostGISSpec" Feature.Query.PostGISSpec.spec
-- this test runs with a root spec function override
parallel $ before rootSpecApp $
describe "Feature.OpenApi.RootSpec" Feature.OpenApi.RootSpec.spec
-- this test runs with a pre request function override
parallel $ before responseHeadersApp $
describe "Feature.RpcPreRequestGucsSpec" Feature.RpcPreRequestGucsSpec.spec
-- this test runs with multiple schemas
parallel $ before multipleSchemaApp $
describe "Feature.Query.MultipleSchemaSpec" Feature.Query.MultipleSchemaSpec.spec
-- this test runs with db-plan-enabled = true
parallel $ before planEnabledApp $
describe "Feature.Query.PlanSpec.spec" Feature.Query.PlanSpec.spec
-- this test runs with server-trace-header set
parallel $ before obsApp $
describe "Feature.ObservabilitySpec.spec" Feature.ObservabilitySpec.spec
parallel $ before serverTiming $
describe "Feature.Query.ServerTimingSpec.spec" Feature.Query.ServerTimingSpec.spec
parallel $ before aggregatesEnabled $
describe "Feature.Query.AggregateFunctionsSpec" Feature.Query.AggregateFunctionsSpec.allowed
-- this test runs with db-timezone-enabled = false
parallel $ before timezoneDisabled $
describe "Feature.Query.Preferences.TimezoneSpec.disabledSpec" Feature.Query.Preferences.TimezoneSpec.disabledSpec
mapM_ parallel specs
parallel $ describe "Feature.Query.UnicodeSpec" $ Feature.Query.UnicodeSpec.spec withConfigDbs
parallel $ describe "Feature.OpenApi.IgnorePrivOpenApiSpec" $ Feature.OpenApi.IgnorePrivOpenApiSpec.spec withConfigDbs
parallel $ describe "Feature.ExtraSearchPathSpec" $ Feature.ExtraSearchPathSpec.spec withConfigDbs
parallel $ describe "Feature.Query.PostGISSpec" $ Feature.Query.PostGISSpec.spec withConfigDbs
parallel $ describe "Feature.Query.MultipleSchemaSpec" $ Feature.Query.MultipleSchemaSpec.spec withConfigDbs
parallel $ describe "Feature.Query.Preferences.TimezoneSpec.disabledSpec" $ Feature.Query.Preferences.TimezoneSpec.disabledSpec withConfigDbs
-- Note: the rollback tests can not run in parallel, because they test persistence and
-- this results in race conditions
-- this test runs with tx-rollback-all = true and tx-allow-override = true
before withApp $
describe "Feature.RollbackAllowedSpec" Feature.RollbackSpec.allowed
-- this test runs with tx-rollback-all = false and tx-allow-override = false
before disallowRollbackApp $
describe "Feature.RollbackDisallowedSpec" Feature.RollbackSpec.disallowed
-- this test runs with tx-rollback-all = true and tx-allow-override = false
before forceRollbackApp $
describe "Feature.RollbackForcedSpec" Feature.RollbackSpec.forced
describe "Feature.RollbackAllowedSpec" $ Feature.RollbackSpec.allowed withConfig
describe "Feature.RollbackDisallowedSpec" $ Feature.RollbackSpec.disallowed withConfig
describe "Feature.RollbackForcedSpec" $ Feature.RollbackSpec.forced withConfig
-- This test runs with a pre request to enable the pg-safeupdate library per-session.
-- This needs to run last, because once pg safe update is loaded, it can't be unloaded again.
before pgSafeUpdateApp $
describe "Feature.Query.PgSafeUpdateSpec.spec" Feature.Query.PgSafeUpdateSpec.spec
describe "Feature.Query.PgSafeUpdateSpec.spec" $ Feature.Query.PgSafeUpdateSpec.spec withConfig
where
loadSCache pool conf =
+3 -96
View File
@@ -14,9 +14,10 @@ import qualified Jose.Jws as JWT
import qualified Jose.Jwt as JWT
import Data.Aeson ((.=))
import Data.CaseInsensitive (CI (..), mk, original)
import Data.CaseInsensitive (CI (..), original)
import Data.List (lookup)
import Data.List.NonEmpty (fromList)
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleBody, simpleHeaders, simpleStatus))
import System.IO.Unsafe (unsafePerformIO)
import Text.Regex.TDFA ((=~))
@@ -25,7 +26,6 @@ import Text.Regex.TDFA ((=~))
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Text.Heredoc
import Data.String (String)
import PostgREST.Config (AppConfig (..),
@@ -106,6 +106,7 @@ validateOpenApiResponse headers = do
, matchHeaders = []
}
type SpecWithConfig = (AppConfig -> SpecWith ((), Application) -> Spec) -> Spec
baseCfg :: AppConfig
baseCfg = let secret = encodeUtf8 "reallyreallyreallyreallyverysafe" in
@@ -161,100 +162,6 @@ baseCfg = let secret = encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configServerTimingEnabled = True
}
testCfg :: AppConfig
testCfg = baseCfg
testCfgDisallowRollback :: AppConfig
testCfgDisallowRollback = baseCfg { configDbTxAllowOverride = False, configDbTxRollbackAll = False }
testCfgForceRollback :: AppConfig
testCfgForceRollback = baseCfg { configDbTxAllowOverride = False, configDbTxRollbackAll = True }
testCfgNoAnon :: AppConfig
testCfgNoAnon = baseCfg { configDbAnonRole = Nothing }
testCfgNoJwtSecret :: AppConfig
testCfgNoJwtSecret = baseCfg { configJwtSecret = Nothing, configJWKS = Nothing }
testUnicodeCfg :: AppConfig
testUnicodeCfg = baseCfg { configDbSchemas = fromList ["تست"] }
testMaxRowsCfg :: AppConfig
testMaxRowsCfg = baseCfg { configDbMaxRows = Just 2 }
testDisabledOpenApiCfg :: AppConfig
testDisabledOpenApiCfg = baseCfg { configOpenApiMode = OADisabled }
testIgnorePrivOpenApiCfg :: AppConfig
testIgnorePrivOpenApiCfg = baseCfg { configOpenApiMode = OAIgnorePriv, configDbSchemas = fromList ["test", "v1"] }
testProxyCfg :: AppConfig
testProxyCfg = baseCfg { configOpenApiServerProxyUri = Just "https://postgrest.com/openapi.json" }
testSecurityOpenApiCfg :: AppConfig
testSecurityOpenApiCfg = baseCfg { configOpenApiSecurityActive = True }
testPlanEnabledCfg :: AppConfig
testPlanEnabledCfg = baseCfg { configDbPlanEnabled = True }
testCfgBinaryJWT :: AppConfig
testCfgBinaryJWT =
baseCfg {
configJwtSecret = Just generateSecret
, configJWKS = rightToMaybe $ parseSecret generateSecret
}
testCfgAudienceJWT :: AppConfig
testCfgAudienceJWT =
baseCfg {
configJwtSecret = Just generateSecret
, configJwtAudience = Just "youraudience"
, configJWKS = rightToMaybe $ parseSecret generateSecret
}
testCfgAsymJWK :: AppConfig
testCfgAsymJWK =
let secret = encodeUtf8 [str|{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}|]
in baseCfg {
configJwtSecret = Just secret
, configJWKS = rightToMaybe $ parseSecret secret
}
testCfgAsymJWKSet :: AppConfig
testCfgAsymJWKSet =
let secret = encodeUtf8 [str|{"keys": [{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kty":"RSA","n":"0etQ2Tg187jb04MWfpuogYGV75IFrQQBxQaGH75eq_FpbkyoLcEpRUEWSbECP2eeFya2yZ9vIO5ScD-lPmovePk4Aa4SzZ8jdjhmAbNykleRPCxMg0481kz6PQhnHRUv3nF5WP479CnObJKqTVdEagVL66oxnX9VhZG9IZA7k0Th5PfKQwrKGyUeTGczpOjaPqbxlunP73j9AfnAt4XCS8epa-n3WGz1j-wfpr_ys57Aq-zBCfqP67UYzNpeI1AoXsJhD9xSDOzvJgFRvc3vm2wjAW4LEMwi48rCplamOpZToIHEPIaPzpveYQwDnB1HFTR1ove9bpKJsHmi-e2uzQ","use":"sig"}]}|]
in baseCfg {
configJwtSecret = Just secret
, configJWKS = rightToMaybe $ parseSecret secret
}
testCfgExtraSearchPath :: AppConfig
testCfgExtraSearchPath = baseCfg { configDbExtraSearchPath = ["public", "extensions", "EXTRA \"@/\\#~_-"] }
testCfgRootSpec :: AppConfig
testCfgRootSpec = baseCfg { configDbRootSpec = Just $ QualifiedIdentifier mempty "root"}
testCfgResponseHeaders :: AppConfig
testCfgResponseHeaders = baseCfg { configDbPreRequest = Just $ QualifiedIdentifier mempty "custom_headers" }
testMultipleSchemaCfg :: AppConfig
testMultipleSchemaCfg = baseCfg { configDbSchemas = fromList ["v1", "v2", "SPECIAL \"@/\\#~_-"] }
testPgSafeUpdateEnabledCfg :: AppConfig
testPgSafeUpdateEnabledCfg = baseCfg { configDbPreRequest = Just $ QualifiedIdentifier "test" "load_safeupdate" }
testObservabilityCfg :: AppConfig
testObservabilityCfg = baseCfg { configServerTraceHeader = Just $ mk "X-Request-Id" }
testCfgServerTiming :: AppConfig
testCfgServerTiming = baseCfg { configDbPlanEnabled = True }
testCfgAggregatesEnabled :: AppConfig
testCfgAggregatesEnabled = baseCfg { configDbAggregates = True }
testCfgTimezoneDisabled :: AppConfig
testCfgTimezoneDisabled = baseCfg { configDbTimezoneEnabled = False }
rangeHdrs :: ByteRange -> [Header]
rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)]