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"