diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b67e877a2..988e30b16 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,9 +63,6 @@ jobs: - name: Run the spec tests against PostgreSQL 9.6 if: always() run: postgrest-with-postgresql-9.6 postgrest-test-spec - - name: Run the spec tests against PostgreSQL 9.5 - if: always() - run: postgrest-with-postgresql-9.5 postgrest-test-spec - name: Run query cost tests against all PostgreSQL versions if: always() diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e8a9c32..1cff9543d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). + Getting the value for a header GUC on PostgreSQL 14 is done using `current_setting('request.headers')::json->>'name-of-header'` and in a similar way for `request.cookies` and `request.jwt.claims` + PostgreSQL versions below 14 can opt in to the new JSON GUCs by setting the `db-use-legacy-gucs` config option to false (true by default) - #1783, Partitions (created using `PARTITION OF`) are no longer included in the schema cache. - @laurenceisla + - #2038, Dropped support for PostgreSQL 9.5 - @wolfgangwalther ## [8.0.0] - 2021-07-25 diff --git a/README.md b/README.md index 3504ef0e4..720b85d71 100644 --- a/README.md +++ b/README.md @@ -112,15 +112,6 @@ the connection cannot do anything the user themselves couldn't. Other forms of authentication can be built on top of the JWT primitive. See the docs for more information. -Since PostgreSQL 9.5 supports true [row-level -security](http://www.postgresql.org/docs/9.5/static/ddl-rowsecurity.html). -In previous versions it can be simulated with triggers and -security-barrier views. Because the possible queries to the database -are limited to certain templates using -[leakproof](http://blog.2ndquadrant.com/how-do-postgresql-security_barrier-views-work/) -functions, the trigger workaround does not compromise row-level -security. - ## Versioning A robust long-lived API needs the freedom to exist in multiple diff --git a/default.nix b/default.nix index 9fa62800e..3c1705359 100644 --- a/default.nix +++ b/default.nix @@ -52,7 +52,6 @@ let { name = "postgresql-11"; postgresql = pkgs.postgresql_11; } { name = "postgresql-10"; postgresql = pkgs.postgresql_10; } { name = "postgresql-9.6"; postgresql = pkgs.postgresql_9_6; } - { name = "postgresql-9.5"; postgresql = pkgs.postgresql_9_5; } ]; patches = diff --git a/nix/README.md b/nix/README.md index 85739da42..614ea04d4 100644 --- a/nix/README.md +++ b/nix/README.md @@ -80,8 +80,8 @@ postgrest-coverage postgrest-with-postgresql-10 postgrest-lint postgrest-with-postgresql-11 postgrest-run postgrest-with-postgresql-12 postgrest-style postgrest-with-postgresql-13 -postgrest-style-check postgrest-with-postgresql-9.5 -postgrest-test-io postgrest-with-postgresql-9.6 +postgrest-style-check postgrest-with-postgresql-9.6 +postgrest-test-io ... [nix-shell]$ @@ -104,8 +104,8 @@ postgrest-coverage postgrest-with-postgresql-10 postgrest-lint postgrest-with-postgresql-11 postgrest-run postgrest-with-postgresql-12 postgrest-style postgrest-with-postgresql-13 -postgrest-style-check postgrest-with-postgresql-9.5 -postgrest-test-io postgrest-with-postgresql-9.6 +postgrest-style-check postgrest-with-postgresql-9.6 +postgrest-test-io postgrest-test-memory ... diff --git a/nix/overlays/postgresql-legacy.nix b/nix/overlays/postgresql-legacy.nix index fe7034c41..6e3afbdd6 100644 --- a/nix/overlays/postgresql-legacy.nix +++ b/nix/overlays/postgresql-legacy.nix @@ -5,16 +5,16 @@ self: super: # PostgreSQL 9.5 was removed from Nixpkgs with # https://github.com/NixOS/nixpkgs/commit/72ab382fb6b729b0d654f2c03f5eb25b39f11fbb # We pin its parent commit to get the last version that was available. - postgresql_9_5 = - let - rev = "55ac7d4580c9ab67848c98cb9519317a1cc399c8"; - tarballHash = "02ffj9f8s1hwhmxj85nx04sv64qb6jm7w0122a1dz9n32fymgklj"; - - pinnedPkgs = - builtins.fetchTarball { - url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz"; - sha256 = tarballHash; - }; - in - (import pinnedPkgs { }).pkgs.postgresql_9_5; + # postgresql_9_5 = + # let + # rev = "55ac7d4580c9ab67848c98cb9519317a1cc399c8"; + # tarballHash = "02ffj9f8s1hwhmxj85nx04sv64qb6jm7w0122a1dz9n32fymgklj"; + # + # pinnedPkgs = + # builtins.fetchTarball { + # url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz"; + # sha256 = tarballHash; + # }; + # in + # (import pinnedPkgs { }).pkgs.postgresql_9_5; } diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 0d503db90..d70ac55ef 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -258,7 +258,6 @@ handleRead headersOnly identifier context@RequestContext{..} = do (shouldCount iPreferCount) (iAcceptContentType == CTTextCSV) bField - ctxPgVersion configDbPreparedStatements total <- readTotal ctxConfig ctxApiRequest tableTotal countQuery @@ -447,7 +446,6 @@ handleInvoke invMethod proc context@RequestContext{..} = do (iAcceptContentType == CTTextCSV) (iPreferParameters == Just MultipleObjects) bField - ctxPgVersion (configDbPreparedStatements ctxConfig) response <- liftEither $ gucResponse <$> gucStatus <*> gucHeaders @@ -532,7 +530,6 @@ writeQuery identifier@QualifiedIdentifier{..} isInsert pkCols context@RequestCon (iAcceptContentType ctxApiRequest == CTTextCSV) (iPreferRepresentation ctxApiRequest) pkCols - ctxPgVersion (configDbPreparedStatements ctxConfig) liftEither $ WriteQueryResult queryTotal fields body <$> gucStatus <*> gucHeaders diff --git a/src/PostgREST/Config/PgVersion.hs b/src/PostgREST/Config/PgVersion.hs index 1f0e8c660..8aa372752 100644 --- a/src/PostgREST/Config/PgVersion.hs +++ b/src/PostgREST/Config/PgVersion.hs @@ -3,7 +3,6 @@ module PostgREST.Config.PgVersion ( PgVersion(..) , minimumPgVersion - , pgVersion95 , pgVersion96 , pgVersion100 , pgVersion109 @@ -31,10 +30,7 @@ instance Ord PgVersion where -- | Tells the minimum PostgreSQL version required by this version of PostgREST minimumPgVersion :: PgVersion -minimumPgVersion = pgVersion95 - -pgVersion95 :: PgVersion -pgVersion95 = PgVersion 90500 "9.5" +minimumPgVersion = pgVersion96 pgVersion96 :: PgVersion pgVersion96 = PgVersion 90600 "9.6" diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index 9bbc1ff0c..e35a8c2dc 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -46,7 +46,6 @@ import qualified Hasql.Encoders as HE import Data.Foldable (foldr1) import Text.InterpolatedString.Perl6 (qc) -import PostgREST.Config.PgVersion (PgVersion, pgVersion96) import PostgREST.DbStructure.Identifiers (FieldName, QualifiedIdentifier (..)) import PostgREST.RangeQuery (NonnegRange, allRange, @@ -315,17 +314,11 @@ limitOffsetF range = limit = maybe "ALL" (\l -> unknownEncoder (BS.pack $ show l)) $ rangeLimit range offset = unknownEncoder (BS.pack . show $ rangeOffset range) -responseHeadersF :: PgVersion -> SqlFragment -responseHeadersF pgVer = - if pgVer >= pgVersion96 - then currentSettingF "response.headers" - else "null" +responseHeadersF :: SqlFragment +responseHeadersF = currentSettingF "response.headers" -responseStatusF :: PgVersion -> SqlFragment -responseStatusF pgVer = - if pgVer >= pgVersion96 - then currentSettingF "response.status" - else "null" +responseStatusF :: SqlFragment +responseStatusF = currentSettingF "response.status" currentSettingF :: SqlFragment -> SqlFragment currentSettingF setting = diff --git a/src/PostgREST/Query/Statements.hs b/src/PostgREST/Query/Statements.hs index 477b707d5..3b2cbee71 100644 --- a/src/PostgREST/Query/Statements.hs +++ b/src/PostgREST/Query/Statements.hs @@ -30,9 +30,8 @@ import Data.Maybe (fromJust) import Data.Text.Read (decimal) import Network.HTTP.Types.Status (Status) -import PostgREST.Config.PgVersion (PgVersion) -import PostgREST.Error (Error (..)) -import PostgREST.GucHeader (GucHeader) +import PostgREST.Error (Error (..)) +import PostgREST.GucHeader (GucHeader) import PostgREST.DbStructure.Identifiers (FieldName) import PostgREST.Query.SqlFragment @@ -47,9 +46,9 @@ import Protolude type ResultsWithCount = (Maybe Int64, Int64, [BS.ByteString], BS.ByteString, Either Error [GucHeader], Either Error (Maybe Status)) createWriteStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> - PreferRepresentation -> [Text] -> PgVersion -> Bool -> + PreferRepresentation -> [Text] -> Bool -> SQL.Statement () ResultsWithCount -createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys pgVer = +createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys = SQL.dynamicallyParameterized snippet decodeStandard where snippet = @@ -60,8 +59,8 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys "pg_catalog.count(_postgrest_t) AS page_total, " <> locF <> " AS header, " <> bodyF <> " AS body, " <> - responseHeadersF pgVer <> " AS response_headers, " <> - responseStatusF pgVer <> " AS response_status " + responseHeadersF <> " AS response_headers, " <> + responseStatusF <> " AS response_status " ) <> "FROM (" <> selectF <> ") _postgrest_t" @@ -89,9 +88,9 @@ createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys decodeStandard = fromMaybe (Nothing, 0, [], mempty, Right [], Right Nothing) <$> HD.rowMaybe standardRow -createReadStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion -> Bool -> +createReadStatement :: SQL.Snippet -> SQL.Snippet -> Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> SQL.Statement () ResultsWithCount -createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField pgVer = +createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField = SQL.dynamicallyParameterized snippet decodeStandard where snippet = @@ -103,8 +102,8 @@ createReadStatement selectQuery countQuery isSingle countTotal asCsv binaryField "pg_catalog.count(_postgrest_t) AS page_total, " <> noLocationF <> " AS header, " <> bodyF <> " AS body, " <> - responseHeadersF pgVer <> " AS response_headers, " <> - responseStatusF pgVer <> " AS response_status " <> + responseHeadersF <> " AS response_headers, " <> + responseStatusF <> " AS response_status " <> "FROM ( SELECT * FROM " <> sourceCTEName <> " ) _postgrest_t") (countCTEF, countResultF) = countF countQuery countTotal @@ -132,9 +131,9 @@ standardRow = (,,,,,) <$> nullableColumn HD.int8 <*> column HD.int8 type ProcResults = (Maybe Int64, Int64, ByteString, Either Error [GucHeader], Either Error (Maybe Status)) callProcStatement :: Bool -> Bool -> SQL.Snippet -> SQL.Snippet -> SQL.Snippet -> Bool -> - Bool -> Bool -> Bool -> Maybe FieldName -> PgVersion -> Bool -> + Bool -> Bool -> Bool -> Maybe FieldName -> Bool -> SQL.Statement () ProcResults -callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal asSingle asCsv multObjects binaryField pgVer = +callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQuery countTotal asSingle asCsv multObjects binaryField = SQL.dynamicallyParameterized snippet decodeProc where snippet = @@ -145,8 +144,8 @@ callProcStatement returnsScalar returnsSingle callProcQuery selectQuery countQue countResultF <> " AS total_result_set, " <> "pg_catalog.count(_postgrest_t) AS page_total, " <> bodyF <> " AS body, " <> - responseHeadersF pgVer <> " AS response_headers, " <> - responseStatusF pgVer <> " AS response_status ") <> + responseHeadersF <> " AS response_headers, " <> + responseStatusF <> " AS response_status ") <> "FROM (" <> selectQuery <> ") _postgrest_t" (countCTEF, countResultF) = countF countQuery countTotal diff --git a/test/Feature/MultipleSchemaSpec.hs b/test/Feature/MultipleSchemaSpec.hs index 58adcda38..b11e28285 100644 --- a/test/Feature/MultipleSchemaSpec.hs +++ b/test/Feature/MultipleSchemaSpec.hs @@ -15,10 +15,8 @@ import Test.Hspec.Wai.JSON import Protolude import SpecHelper -import PostgREST.Config.PgVersion (PgVersion, pgVersion96) - -spec :: PgVersion -> SpecWith ((), Application) -spec actualPgVersion = +spec :: SpecWith ((), Application) +spec = 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" $ @@ -191,16 +189,15 @@ spec actualPgVersion = [json|[{"id": 1, "name": "child v2-3", "parent_id": 3}]|] { matchHeaders = ["Content-Profile" <:> "v2"] } - when (actualPgVersion >= pgVersion96) $ - it "succeeds on PUT on the v2 schema" $ - request methodPut "/children?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")] - [json| [ { "id": 111, "name": "child v2-111", "parent_id": null } ]|] - `shouldRespondWith` - [json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|] - { - matchStatus = 200 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] - } + it "succeeds on PUT on the v2 schema" $ + request methodPut "/children?id=eq.111" [("Content-Profile", "v2"), ("Prefer", "return=representation")] + [json| [ { "id": 111, "name": "child v2-111", "parent_id": null } ]|] + `shouldRespondWith` + [json|[{ "id": 111, "name": "child v2-111", "parent_id": null }]|] + { + matchStatus = 200 + , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] + } context "OpenAPI output" $ do it "succeeds in reading table definition from default schema v1 if no schema is selected via header" $ do diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 5bfe6890f..722deea40 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -9,8 +9,7 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import PostgREST.Config.PgVersion (PgVersion, pgVersion110, - pgVersion112, pgVersion121, - pgVersion96) + pgVersion112, pgVersion121) import Protolude hiding (get) import SpecHelper @@ -200,34 +199,33 @@ spec actualPgVersion = do {"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|] { matchHeaders = [matchContentTypeJson] } - when (actualPgVersion >= pgVersion96) $ - context "Use of the phraseto_tsquery function" $ do - it "finds matches" $ - get "/tsearch?text_search_vector=phfts.The%20Fat%20Cats" `shouldRespondWith` - [json| [{"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |] - { matchHeaders = [matchContentTypeJson] } + context "Use of the phraseto_tsquery function" $ do + it "finds matches" $ + get "/tsearch?text_search_vector=phfts.The%20Fat%20Cats" `shouldRespondWith` + [json| [{"text_search_vector": "'ate':3 'cat':2 'fat':1 'rat':4" }] |] + { matchHeaders = [matchContentTypeJson] } - it "finds matches with different dictionaries" $ - get "/tsearch?text_search_vector=phfts(german).Art%20Spass" `shouldRespondWith` - [json| [{"text_search_vector": "'art':4 'spass':5 'unmog':7" }] |] - { matchHeaders = [matchContentTypeJson] } + it "finds matches with different dictionaries" $ + get "/tsearch?text_search_vector=phfts(german).Art%20Spass" `shouldRespondWith` + [json| [{"text_search_vector": "'art':4 'spass':5 'unmog':7" }] |] + { matchHeaders = [matchContentTypeJson] } - it "can be negated with not operator" $ - get "/tsearch?text_search_vector=not.phfts(english).The%20Fat%20Cats" `shouldRespondWith` - [json| [ - {"text_search_vector": "'fun':5 'imposs':9 'kind':3"}, - {"text_search_vector": "'also':2 'fun':3 'possibl':8"}, - {"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"}, - {"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|] - { matchHeaders = [matchContentTypeJson] } + it "can be negated with not operator" $ + get "/tsearch?text_search_vector=not.phfts(english).The%20Fat%20Cats" `shouldRespondWith` + [json| [ + {"text_search_vector": "'fun':5 'imposs':9 'kind':3"}, + {"text_search_vector": "'also':2 'fun':3 'possibl':8"}, + {"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4"}, + {"text_search_vector": "'art':4 'spass':5 'unmog':7"}]|] + { matchHeaders = [matchContentTypeJson] } - it "can be used with or query param" $ - get "/tsearch?or=(text_search_vector.phfts(german).Art%20Spass, text_search_vector.phfts(french).amusant, text_search_vector.fts(english).impossible)" `shouldRespondWith` - [json|[ - {"text_search_vector": "'fun':5 'imposs':9 'kind':3" }, - {"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }, - {"text_search_vector": "'art':4 'spass':5 'unmog':7"} - ]|] { matchHeaders = [matchContentTypeJson] } + it "can be used with or query param" $ + get "/tsearch?or=(text_search_vector.phfts(german).Art%20Spass, text_search_vector.phfts(french).amusant, text_search_vector.fts(english).impossible)" `shouldRespondWith` + [json|[ + {"text_search_vector": "'fun':5 'imposs':9 'kind':3" }, + {"text_search_vector": "'amus':5 'fair':7 'impossibl':9 'peu':4" }, + {"text_search_vector": "'art':4 'spass':5 'unmog':7"} + ]|] { matchHeaders = [matchContentTypeJson] } it "matches with computed column" $ get "/items?always_true=eq.true&order=id.asc" `shouldRespondWith` diff --git a/test/Feature/RpcSpec.hs b/test/Feature/RpcSpec.hs index 7f8482167..c6c9ce7db 100644 --- a/test/Feature/RpcSpec.hs +++ b/test/Feature/RpcSpec.hs @@ -15,7 +15,7 @@ import Text.Heredoc import PostgREST.Config.PgVersion (PgVersion, pgVersion100, pgVersion109, pgVersion110, pgVersion112, pgVersion114, - pgVersion140, pgVersion96) + pgVersion140) import Protolude hiding (get) import SpecHelper @@ -1005,72 +1005,70 @@ spec actualPgVersion = [json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|] { matchHeaders = [matchContentTypeJson] } - when (actualPgVersion >= pgVersion96) $ - it "should work with the phraseto_tsquery function" $ - get "/rpc/get_tsearch?text_search_vector=phfts(english).impossible" `shouldRespondWith` - [json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|] - { matchHeaders = [matchContentTypeJson] } + it "should work with the phraseto_tsquery function" $ + get "/rpc/get_tsearch?text_search_vector=phfts(english).impossible" `shouldRespondWith` + [json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|] + { matchHeaders = [matchContentTypeJson] } it "should work with an argument of custom type in public schema" $ get "/rpc/test_arg?my_arg=something" `shouldRespondWith` [json|"foobar"|] { matchHeaders = [matchContentTypeJson] } - when (actualPgVersion >= pgVersion96) $ do - context "GUC headers on function calls" $ do - it "succeeds setting the headers" $ do - get "/rpc/get_projects_and_guc_headers?id=eq.2&select=id" - `shouldRespondWith` [json|[{"id": 2}]|] - {matchHeaders = [ - matchContentTypeJson, - "X-Test" <:> "key1=val1; someValue; key2=val2", - "X-Test-2" <:> "key1=val1"]} - get "/rpc/get_int_and_guc_headers?num=1" - `shouldRespondWith` [json|1|] - {matchHeaders = [ - matchContentTypeJson, - "X-Test" <:> "key1=val1; someValue; key2=val2", - "X-Test-2" <:> "key1=val1"]} - post "/rpc/get_int_and_guc_headers" [json|{"num": 1}|] - `shouldRespondWith` [json|1|] - {matchHeaders = [ - matchContentTypeJson, - "X-Test" <:> "key1=val1; someValue; key2=val2", - "X-Test-2" <:> "key1=val1"]} + context "GUC headers on function calls" $ do + it "succeeds setting the headers" $ do + get "/rpc/get_projects_and_guc_headers?id=eq.2&select=id" + `shouldRespondWith` [json|[{"id": 2}]|] + {matchHeaders = [ + matchContentTypeJson, + "X-Test" <:> "key1=val1; someValue; key2=val2", + "X-Test-2" <:> "key1=val1"]} + get "/rpc/get_int_and_guc_headers?num=1" + `shouldRespondWith` [json|1|] + {matchHeaders = [ + matchContentTypeJson, + "X-Test" <:> "key1=val1; someValue; key2=val2", + "X-Test-2" <:> "key1=val1"]} + post "/rpc/get_int_and_guc_headers" [json|{"num": 1}|] + `shouldRespondWith` [json|1|] + {matchHeaders = [ + matchContentTypeJson, + "X-Test" <:> "key1=val1; someValue; key2=val2", + "X-Test-2" <:> "key1=val1"]} - it "fails when setting headers with wrong json structure" $ do - get "/rpc/bad_guc_headers_1" - `shouldRespondWith` - [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] - { matchStatus = 500 - , matchHeaders = [ matchContentTypeJson ] - } - get "/rpc/bad_guc_headers_2" - `shouldRespondWith` - [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] - { matchStatus = 500 - , matchHeaders = [ matchContentTypeJson ] - } - get "/rpc/bad_guc_headers_3" - `shouldRespondWith` - [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] - { matchStatus = 500 - , matchHeaders = [ matchContentTypeJson ] - } - post "/rpc/bad_guc_headers_1" [json|{}|] - `shouldRespondWith` - [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] - { matchStatus = 500 - , matchHeaders = [ matchContentTypeJson ] - } + it "fails when setting headers with wrong json structure" $ do + get "/rpc/bad_guc_headers_1" + `shouldRespondWith` + [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] + { matchStatus = 500 + , matchHeaders = [ matchContentTypeJson ] + } + get "/rpc/bad_guc_headers_2" + `shouldRespondWith` + [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] + { matchStatus = 500 + , matchHeaders = [ matchContentTypeJson ] + } + get "/rpc/bad_guc_headers_3" + `shouldRespondWith` + [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] + { matchStatus = 500 + , matchHeaders = [ matchContentTypeJson ] + } + post "/rpc/bad_guc_headers_1" [json|{}|] + `shouldRespondWith` + [json|{"message":"response.headers guc must be a JSON array composed of objects with a single key and a string value"}|] + { matchStatus = 500 + , matchHeaders = [ matchContentTypeJson ] + } - it "can set the same http header twice" $ - get "/rpc/set_cookie_twice" - `shouldRespondWith` - "null" - { matchHeaders = [ matchContentTypeJson - , "Set-Cookie" <:> "sessionid=38afes7a8; HttpOnly; Path=/" - , "Set-Cookie" <:> "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly" ]} + it "can set the same http header twice" $ + get "/rpc/set_cookie_twice" + `shouldRespondWith` + "null" + { matchHeaders = [ matchContentTypeJson + , "Set-Cookie" <:> "sessionid=38afes7a8; HttpOnly; Path=/" + , "Set-Cookie" <:> "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly" ]} it "can override the Location header on a trigger" $ post "/stuff" diff --git a/test/Main.hs b/test/Main.hs index c5b4b84e3..a292efd6a 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -9,13 +9,12 @@ import Data.List.NonEmpty (toList) import Test.Hspec -import PostgREST.App (postgrest) -import PostgREST.Config (AppConfig (..), LogLevel (..)) -import PostgREST.Config.Database (queryPgVersion) -import PostgREST.Config.PgVersion (pgVersion96) -import PostgREST.DbStructure (queryDbStructure) -import Protolude hiding (toList, toS) -import Protolude.Conv (toS) +import PostgREST.App (postgrest) +import PostgREST.Config (AppConfig (..), LogLevel (..)) +import PostgREST.Config.Database (queryPgVersion) +import PostgREST.DbStructure (queryDbStructure) +import Protolude hiding (toList, toS) +import Protolude.Conv (toS) import SpecHelper import qualified PostgREST.AppState as AppState @@ -203,16 +202,15 @@ main = do parallel $ before extraSearchPathApp $ describe "Feature.ExtraSearchPathSpec" Feature.ExtraSearchPathSpec.spec - when (actualPgVersion >= pgVersion96) $ do - -- this test runs with a root spec function override - parallel $ before rootSpecApp $ - describe "Feature.RootSpec" Feature.RootSpec.spec - parallel $ before responseHeadersApp $ - describe "Feature.RpcPreRequestGucsSpec" Feature.RpcPreRequestGucsSpec.spec + -- this test runs with a root spec function override + parallel $ before rootSpecApp $ + describe "Feature.RootSpec" Feature.RootSpec.spec + parallel $ before responseHeadersApp $ + describe "Feature.RpcPreRequestGucsSpec" Feature.RpcPreRequestGucsSpec.spec -- this test runs with multiple schemas parallel $ before multipleSchemaApp $ - describe "Feature.MultipleSchemaSpec" $ Feature.MultipleSchemaSpec.spec actualPgVersion + describe "Feature.MultipleSchemaSpec" Feature.MultipleSchemaSpec.spec -- this test runs with db-uses-legacy-gucs = false parallel $ before testCfgLegacyGucsApp $