diff --git a/CHANGELOG.md b/CHANGELOG.md index 1788c3a2b..647883820 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther + - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index d70ac55ef..89a75bbe9 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -374,7 +374,7 @@ handleSingleUpsert identifier context@(RequestContext _ _ ApiRequest{..} _) = do if iPreferRepresentation == Full then response HTTP.status200 (contentTypeHeaders context) (LBS.fromStrict resBody) else - response HTTP.status204 (contentTypeHeaders context) mempty + response HTTP.status204 [] mempty handleDelete :: QualifiedIdentifier -> RequestContext -> DbHandler Wai.Response handleDelete identifier context@(RequestContext _ _ ApiRequest{..} _) = do diff --git a/test/Feature/DeleteSpec.hs b/test/Feature/DeleteSpec.hs index 86bc89842..c695e462c 100644 --- a/test/Feature/DeleteSpec.hs +++ b/test/Feature/DeleteSpec.hs @@ -7,18 +7,23 @@ 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 "Deleting" $ do context "existing record" $ do it "succeeds with 204 and deletion count" $ - request methodDelete "/items?id=eq.1" [] "" - `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodDelete "/items?id=eq.1" + [] + "" + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } it "returns the deleted item and count if requested" $ request methodDelete "/items?id=eq.2" [("Prefer", "return=representation"), ("Prefer", "count=exact")] "" @@ -28,16 +33,24 @@ spec = } it "ignores ?select= when return not set or return=minimal" $ do - request methodDelete "/items?id=eq.3&select=id" [] "" - `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["Content-Range" <:> "*/*"] - } - request methodDelete "/items?id=eq.3&select=id" [("Prefer", "return=minimal")] "" - `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodDelete "/items?id=eq.3&select=id" + [] + "" + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } + request methodDelete "/items?id=eq.3&select=id" + [("Prefer", "return=minimal")] + "" + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } it "returns the deleted item and shapes the response" $ request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] "" @@ -84,9 +97,21 @@ spec = } it "suceeds deleting the row with no explicit select when using return=minimal" $ - request methodDelete "/app_users?id=eq.2" [("Prefer", "return=minimal")] mempty - `shouldRespondWith` 204 + request methodDelete "/app_users?id=eq.2" + [("Prefer", "return=minimal")] + mempty + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } it "suceeds deleting the row with no explicit select by default" $ - request methodDelete "/app_users?id=eq.3" [] mempty - `shouldRespondWith` 204 + request methodDelete "/app_users?id=eq.3" + [] + mempty + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } diff --git a/test/Feature/IgnorePrivOpenApiSpec.hs b/test/Feature/IgnorePrivOpenApiSpec.hs index 342143c32..6b070e2a2 100644 --- a/test/Feature/IgnorePrivOpenApiSpec.hs +++ b/test/Feature/IgnorePrivOpenApiSpec.hs @@ -19,8 +19,14 @@ spec :: SpecWith ((), Application) spec = describe "OpenAPI Ignore Privileges" $ do it "root path returns a valid openapi spec" $ do validateOpenApiResponse [("Accept", "application/openapi+json")] - request methodHead "/" (acceptHdrs "application/openapi+json") "" - `shouldRespondWith` "" { matchStatus = 200 } + request methodHead "/" + (acceptHdrs "application/openapi+json") + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = [ "Content-Type" <:> "application/openapi+json; charset=utf-8" ] + } describe "table" $ do diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index b05dd8068..b7aa31e89 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -45,20 +45,30 @@ spec actualPgVersion = do } it "ignores &select when return not set or using return=minimal" $ do - request methodPost "/menagerie?select=integer,varchar" [] - [json| [{ - "integer": 15, "double": 3.14159, "varchar": "testing!" - , "boolean": false, "date": "1900-01-01", "money": "$3.99" - , "enum": "foo" - }] |] `shouldRespondWith` "" - { matchStatus = 201 } - request methodPost "/menagerie?select=integer,varchar" [("Prefer", "return=minimal")] - [json| [{ - "integer": 16, "double": 3.14159, "varchar": "testing!" - , "boolean": false, "date": "1900-01-01", "money": "$3.99" - , "enum": "foo" - }] |] `shouldRespondWith` "" - { matchStatus = 201 } + request methodPost "/menagerie?select=integer,varchar" + [] + [json| [{ + "integer": 15, "double": 3.14159, "varchar": "testing!", + "boolean": false, "date": "1900-01-01", "money": "$3.99", + "enum": "foo" + }] |] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } + request methodPost "/menagerie?select=integer,varchar" + [("Prefer", "return=minimal")] + [json| [{ + "integer": 16, "double": 3.14159, "varchar": "testing!", + "boolean": false, "date": "1900-01-01", "money": "$3.99", + "enum": "foo" + }] |] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } context "non uniform json array" $ do it "rejects json array that isn't exclusivily composed of objects" $ @@ -113,29 +123,41 @@ spec actualPgVersion = do context "requesting headers only representation" $ do it "should not throw and return location header when selecting without PK" $ - request methodPost "/projects?select=name,client_id" [("Prefer", "return=headers-only")] - [json|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = [ "Location" <:> "/projects?id=eq.11" - , "Content-Range" <:> "*/*" ] - } - - when (actualPgVersion >= pgVersion110) $ - it "should not throw and return location header for partitioned tables when selecting without PK" $ - request methodPost "/car_models" [("Prefer", "return=headers-only")] - [json|{"name":"Enzo","year":2021}|] `shouldRespondWith` "" + request methodPost "/projects?select=name,client_id" + [("Prefer", "return=headers-only")] + [json|{"id":11,"name":"New Project","client_id":2}|] + `shouldRespondWith` + "" { matchStatus = 201 - , matchHeaders = [ "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021" + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/projects?id=eq.11" , "Content-Range" <:> "*/*" ] } + when (actualPgVersion >= pgVersion110) $ + it "should not throw and return location header for partitioned tables when selecting without PK" $ + request methodPost "/car_models" + [("Prefer", "return=headers-only")] + [json|{"name":"Enzo","year":2021}|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021" + , "Content-Range" <:> "*/*" ] + } + context "requesting no representation" $ it "should not throw and return no location header when selecting without PK" $ - request methodPost "/projects?select=name,client_id" [] - [json|{"id":12,"name":"New Project","client_id":2}|] `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = [ matchHeaderAbsent hLocation ] - } + request methodPost "/projects?select=name,client_id" + [] + [json|{"id":12,"name":"New Project","client_id":2}|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [ matchHeaderAbsent hContentType + , matchHeaderAbsent hLocation ] + } context "from an html form" $ it "accepts disparate json types" $ do @@ -145,7 +167,9 @@ spec actualPgVersion = do "boolean=false&date=1900-01-01&money=$3.99&enum=foo") `shouldRespondWith` "" - { matchStatus = 201 } + { matchStatus = 201 + , matchHeaders = [ matchHeaderAbsent hContentType ] + } context "with no pk supplied" $ do context "into a table with auto-incrementing pk" $ @@ -157,12 +181,14 @@ spec actualPgVersion = do `shouldRespondWith` [json|""|] - request methodPost "/auto_incrementing_pk" [("Prefer", "return=headers-only")] + request methodPost "/auto_incrementing_pk" + [("Prefer", "return=headers-only")] [json| { "non_nullable_string":"not null"} |] `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [ "Location" <:> "/auto_incrementing_pk?id=eq.2" ] + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/auto_incrementing_pk?id=eq.2" ] } context "into a table with simple pk" $ @@ -180,11 +206,13 @@ spec actualPgVersion = do context "into a table with no pk" $ do it "succeeds with 201 but no location header" $ do - post "/no_pk" [json| { "a":"foo", "b":"bar" } |] + post "/no_pk" + [json| { "a":"foo", "b":"bar" } |] `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [matchHeaderAbsent hLocation] + , matchHeaders = [ matchHeaderAbsent hContentType + , matchHeaderAbsent hLocation ] } it "returns full details of inserted record if asked" $ do @@ -231,11 +259,14 @@ spec actualPgVersion = do let bulkData = [json| [ {"k1":21, "k2":"hello world"} , {"k1":22, "k2":"bye for now"}] |] - request methodPost "/compound_pk" [] bulkData + request methodPost "/compound_pk" + [] + bulkData `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [matchHeaderAbsent hLocation] + , matchHeaders = [ matchHeaderAbsent hContentType + , matchHeaderAbsent hLocation ] } context "with invalid json payload" $ @@ -258,7 +289,13 @@ spec actualPgVersion = do context "with valid json payload" $ it "succeeds and returns 201 created" $ - post "/simple_pk" [json| { "k":"k1", "extra":"e1" } |] `shouldRespondWith` 201 + post "/simple_pk" + [json| { "k":"k1", "extra":"e1" } |] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } context "attempting to insert a row with the same primary key" $ it "fails returning a 409 Conflict" $ @@ -293,20 +330,26 @@ spec actualPgVersion = do context "empty objects" $ do it "successfully inserts a row with all-default columns" $ do - post "/items" "{}" `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = [] - } + post "/items" + [json|{}|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } post "/items" "[{}]" `shouldRespondWith` "" { matchStatus = 201 , matchHeaders = [] } it "successfully inserts two rows with all-default columns" $ - post "/items" "[{}, {}]" `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = [] - } + post "/items" + [json|[{}, {}]|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } it "successfully inserts a row with all-default columns with prefer=rep" $ do -- reset pk sequence first to make test repeatable @@ -455,7 +498,10 @@ spec actualPgVersion = do [("Prefer", "tx=commit")] "" `shouldRespondWith` - 204 + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } describe "Row level permission" $ it "set user_id when inserting rows" $ do @@ -523,7 +569,9 @@ spec actualPgVersion = do [json| { "v":"some value" } |] `shouldRespondWith` "" - { matchStatus = 201 } + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] + } describe "Inserting into VIEWs" $ do context "requesting no representation" $ @@ -533,7 +581,8 @@ spec actualPgVersion = do `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [ matchHeaderAbsent hLocation ] + , matchHeaders = [ matchHeaderAbsent hContentType + , matchHeaderAbsent hLocation ] } context "requesting header only representation" $ do @@ -543,7 +592,8 @@ spec actualPgVersion = do `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [ "Location" <:> "/compound_pk_view?k1=eq.1&k2=eq.test" + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/compound_pk_view?k1=eq.1&k2=eq.test" , "Content-Range" <:> "*/*" ] } @@ -553,6 +603,7 @@ spec actualPgVersion = do `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = [ "Location" <:> "/test_null_pk_competitors_sponsors?id=eq.1&sponsor_id=is.null" + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/test_null_pk_competitors_sponsors?id=eq.1&sponsor_id=is.null" , "Content-Range" <:> "*/*" ] } diff --git a/test/Feature/OpenApiSpec.hs b/test/Feature/OpenApiSpec.hs index e594bb04e..26abc1ab3 100644 --- a/test/Feature/OpenApiSpec.hs +++ b/test/Feature/OpenApiSpec.hs @@ -22,8 +22,13 @@ spec :: PgVersion -> SpecWith ((), Application) spec actualPgVersion = describe "OpenAPI" $ do it "root path returns a valid openapi spec" $ do validateOpenApiResponse [("Accept", "application/openapi+json")] - request methodHead "/" (acceptHdrs "application/openapi+json") "" - `shouldRespondWith` "" { matchStatus = 200 } + request methodHead "/" + (acceptHdrs "application/openapi+json") "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/openapi+json; charset=utf-8"] + } it "should respond to openapi request on none root path with 415" $ request methodGet "/items" diff --git a/test/Feature/QueryLimitedSpec.hs b/test/Feature/QueryLimitedSpec.hs index 97d37d012..006f652b3 100644 --- a/test/Feature/QueryLimitedSpec.hs +++ b/test/Feature/QueryLimitedSpec.hs @@ -47,23 +47,33 @@ spec = context "count=estimated" $ do it "uses the query planner guess when query rows > maxRows" $ - request methodHead "/getallprojects_view" [("Prefer", "count=estimated")] "" + request methodHead "/getallprojects_view" + [("Prefer", "count=estimated")] + "" `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/2019"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-1/2019" ] } it "gives exact count when query rows <= maxRows" $ - request methodHead "/getallprojects_view?id=lt.3" [("Prefer", "count=estimated")] "" + request methodHead "/getallprojects_view?id=lt.3" + [("Prefer", "count=estimated")] + "" `shouldRespondWith` "" - { matchHeaders = ["Content-Range" <:> "0-1/2"] } + { matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-1/2" ] + } it "only uses the query planner guess if it's indeed greater than the exact count" $ - request methodHead "/get_projects_above_view" [("Prefer", "count=estimated")] "" + request methodHead "/get_projects_above_view" + [("Prefer", "count=estimated")] + "" `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/3"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-1/3" ] } diff --git a/test/Feature/RangeSpec.hs b/test/Feature/RangeSpec.hs index 4ce0f6cf5..05d95163a 100644 --- a/test/Feature/RangeSpec.hs +++ b/test/Feature/RangeSpec.hs @@ -109,11 +109,10 @@ spec = do context "when I don't want the count" $ do it "returns range Content-Range with /*" $ request methodGet "/menagerie" - [("Prefer", "count=none")] "" - `shouldRespondWith` "[]" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "*/*"] - } + [("Prefer", "count=none")] "" + `shouldRespondWith` + [json|[]|] + { matchHeaders = ["Content-Range" <:> "*/*"] } it "returns range Content-Range with range/*" $ request methodGet "/items?order=id" @@ -163,11 +162,15 @@ spec = do { matchStatus = 200 , matchHeaders = ["Content-Range" <:> "2-4/*"] } - request methodHead "/items?select=id&order=id.asc&limit=3&offset=2" [] mempty - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "2-4/*"] - } + request methodHead "/items?select=id&order=id.asc&limit=3&offset=2" + [] + mempty + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "2-4/*" ] + } it "succeeds if offset equals 0 as a no-op" $ get "/items?select=id&offset=0&order=id" @@ -231,7 +234,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-14/15"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-14/15" ] } request methodHead "/child_entities" @@ -240,7 +244,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-5/6"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-5/6" ] } request methodHead "/getallprojects_view" @@ -249,7 +254,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-4/2019"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-4/2019" ] } it "ignores limit/offset on the planned count" $ do @@ -259,7 +265,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "3-4/15"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "3-4/15" ] } request methodHead "/child_entities?limit=2" @@ -268,7 +275,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/6"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-1/6" ] } request methodHead "/getallprojects_view?limit=2" @@ -277,7 +285,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/2019"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-1/2019" ] } it "works with two levels" $ @@ -287,7 +296,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-5/6"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-5/6" ] } context "with range headers" $ do diff --git a/test/Feature/RollbackSpec.hs b/test/Feature/RollbackSpec.hs index 5ed05b9ca..a2f96d6e6 100644 --- a/test/Feature/RollbackSpec.hs +++ b/test/Feature/RollbackSpec.hs @@ -7,7 +7,8 @@ import Test.Hspec import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import Protolude hiding (get) +import Protolude hiding (get) +import SpecHelper -- two helpers functions to make sure that each test can setup and cleanup properly @@ -18,7 +19,8 @@ postItem = [json|{"id":0}|] `shouldRespondWith` "" - { matchStatus = 201 } + { matchStatus = 201 + , matchHeaders = [matchHeaderAbsent hContentType] } -- removes Items left over from POST, PUT, and PATCH deleteItems = @@ -27,7 +29,8 @@ deleteItems = "" `shouldRespondWith` "" - { matchStatus = 204 } + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] } preferDefault = [("Prefer", "return=representation")] preferCommit = [("Prefer", "return=representation"), ("Prefer", "tx=commit")] @@ -52,7 +55,7 @@ shouldRespondToReads reqHeaders respHeaders = do "" `shouldRespondWith` "" - { matchHeaders = respHeaders } + { matchHeaders = matchContentTypeJson : respHeaders } it "responds to GET on RPC" $ do request methodGet "/rpc/search?id=1" diff --git a/test/Feature/RpcPreRequestGucsSpec.hs b/test/Feature/RpcPreRequestGucsSpec.hs index c615b0c23..548ce18e5 100644 --- a/test/Feature/RpcPreRequestGucsSpec.hs +++ b/test/Feature/RpcPreRequestGucsSpec.hs @@ -7,7 +7,8 @@ import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai import Test.Hspec.Wai.JSON -import Protolude hiding (get, put) +import Protolude hiding (get, put) +import SpecHelper spec :: SpecWith ((), Application) spec = @@ -15,9 +16,11 @@ spec = it "succeeds setting the headers on POST" $ post "/items" [json|[{"id": 11111}]|] - `shouldRespondWith` "" + `shouldRespondWith` + "" { matchStatus = 201 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "X-Custom-Header" <:> "mykey=myval" ] } it "succeeds setting the headers on GET and HEAD" $ do @@ -33,29 +36,37 @@ spec = "" `shouldRespondWith` "" - { matchHeaders = ["Cache-Control" <:> "no-cache, no-store, must-revalidate"] } + { matchHeaders = [ matchContentTypeJson + , "Cache-Control" <:> "no-cache, no-store, must-revalidate" ] + } request methodHead "/projects" [("Accept", "text/csv")] "" `shouldRespondWith` "" - { matchHeaders = ["Content-Disposition" <:> "attachment; filename=projects.csv"] } + { matchHeaders = [ "Content-Type" <:> "text/csv; charset=utf-8" + , "Content-Disposition" <:> "attachment; filename=projects.csv" ] + } it "succeeds setting the headers on PATCH" $ patch "/items?id=eq.1" [json|[{"id": 11111}]|] - `shouldRespondWith` "" + `shouldRespondWith` + "" { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "X-Custom-Header" <:> "mykey=myval" ] } it "succeeds setting the headers on PUT" $ put "/items?id=eq.1" [json|[{"id": 1}]|] - `shouldRespondWith` "" + `shouldRespondWith` + "" { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "X-Custom-Header" <:> "mykey=myval" ] } it "succeeds setting the headers on DELETE" $ @@ -63,7 +74,8 @@ spec = `shouldRespondWith` "" { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "X-Custom-Header" <:> "mykey=myval" ] } it "can override the Content-Type header" $ do request methodHead "/clients?id=eq.1" diff --git a/test/Feature/RpcSpec.hs b/test/Feature/RpcSpec.hs index b0fc0f7ae..f14bbfc90 100644 --- a/test/Feature/RpcSpec.hs +++ b/test/Feature/RpcSpec.hs @@ -38,10 +38,12 @@ spec actualPgVersion = , matchHeaders = ["Content-Range" <:> "0-0/*"] } request methodHead "/rpc/getitemrange?min=2&max=4" - (rangeHdrs (ByteRangeFromTo 0 0)) "" - `shouldRespondWith` "" + (rangeHdrs (ByteRangeFromTo 0 0)) "" + `shouldRespondWith` + "" { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-0/*"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-0/*" ] } it "includes total count if requested" $ do @@ -59,10 +61,12 @@ spec actualPgVersion = , matchHeaders = ["Content-Range" <:> "0-0/2"] } request methodHead "/rpc/getitemrange?min=2&max=4" - (rangeHdrsWithCount (ByteRangeFromTo 0 0)) "" - `shouldRespondWith` "" + (rangeHdrsWithCount (ByteRangeFromTo 0 0)) "" + `shouldRespondWith` + "" { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-0/2"] + , matchHeaders = [ matchContentTypeJson + , "Content-Range" <:> "0-0/2" ] } it "returns proper json" $ do @@ -89,7 +93,8 @@ spec actualPgVersion = } request methodHead "/rpc/getitemrange?min=2&max=4" (acceptHdrs "text/csv") "" - `shouldRespondWith` "" + `shouldRespondWith` + "" { matchStatus = 200 , matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8"] } @@ -328,8 +333,7 @@ spec actualPgVersion = post "/rpc/ret_void" [json|{}|] `shouldRespondWith` - "null" - { matchHeaders = [matchContentTypeJson] } + [json|null|] it "returns null for an integer with null value" $ post "/rpc/ret_null" @@ -1081,7 +1085,8 @@ spec actualPgVersion = `shouldRespondWith` "" { matchStatus = 201 - , matchHeaders = ["Location" <:> "/stuff?id=eq.2&overriden=true"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "Location" <:> "/stuff?id=eq.2&overriden=true" ] } -- On https://github.com/PostgREST/postgrest/issues/1427#issuecomment-595907535 diff --git a/test/Feature/SingularSpec.hs b/test/Feature/SingularSpec.hs index e80ba755f..a6645f73b 100644 --- a/test/Feature/SingularSpec.hs +++ b/test/Feature/SingularSpec.hs @@ -11,7 +11,6 @@ import Test.Hspec.Wai.JSON import Protolude hiding (get) import SpecHelper - spec :: SpecWith ((), Application) spec = describe "Requesting singular json object" $ do @@ -61,7 +60,9 @@ spec = [json| { address: "C Street" } |] `shouldRespondWith` "" - { matchStatus = 204 } + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } it "raises an error for multiple rows" $ do request methodPatch "/addresses" @@ -128,9 +129,11 @@ spec = request methodPost "/addresses" [("Prefer", "return=minimal"), singular] [json| [ { id: 103, address: "xxx" } ] |] - `shouldRespondWith` "" + `shouldRespondWith` + "" { matchStatus = 201 - , matchHeaders = ["Content-Range" <:> "*/*"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] } it "raises an error when attempting to create multiple entities" $ do diff --git a/test/Feature/UnicodeSpec.hs b/test/Feature/UnicodeSpec.hs index 532c4ce33..9391836ac 100644 --- a/test/Feature/UnicodeSpec.hs +++ b/test/Feature/UnicodeSpec.hs @@ -7,7 +7,8 @@ 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 = @@ -31,4 +32,7 @@ spec = [("Prefer", "tx=commit")] "" `shouldRespondWith` - 204 + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } diff --git a/test/Feature/UpdateSpec.hs b/test/Feature/UpdateSpec.hs index e511be2fa..41371fc26 100644 --- a/test/Feature/UpdateSpec.hs +++ b/test/Feature/UpdateSpec.hs @@ -22,11 +22,12 @@ spec = do context "on an empty table" $ it "indicates no records found to update by returning 404" $ request methodPatch "/empty_table" [] - [json| { "extra":20 } |] - `shouldRespondWith` "" - { matchStatus = 404, - matchHeaders = [] - } + [json| { "extra":20 } |] + `shouldRespondWith` + "" + { matchStatus = 404, + matchHeaders = [matchHeaderAbsent hContentType] + } context "with invalid json payload" $ it "fails with 400 and error" $ @@ -53,7 +54,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 204 - , matchHeaders = ["Content-Range" <:> "0-0/*"] + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "0-0/*" ] } it "returns empty array when no rows updated and return=rep" $ @@ -89,7 +91,8 @@ spec = do `shouldRespondWith` "" { matchStatus = 204 - , matchHeaders = ["Content-Range" <:> "0-1/*" + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "0-1/*" , "Preference-Applied" <:> "tx=commit" ] } @@ -103,7 +106,10 @@ spec = do [("Prefer", "tx=commit")] [json| { b: "0" } |] `shouldRespondWith` - 204 + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } it "can set a column to NULL" $ do request methodPatch "/no_pk?a=eq.1" @@ -169,63 +175,86 @@ spec = do { matchHeaders = [matchContentTypeJson] } it "ignores ?select= when return not set or return=minimal" $ do - request methodPatch "/items?id=eq.1&select=id" [] [json| { id:1 } |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "0-0/*"] - } - request methodPatch "/items?id=eq.1&select=id" [("Prefer", "return=minimal")] [json| { id:1 } |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "0-0/*"] - } + request methodPatch "/items?id=eq.1&select=id" + [] [json| { id:1 } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "0-0/*" ] + } + request methodPatch "/items?id=eq.1&select=id" + [("Prefer", "return=minimal")] + [json| { id:1 } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "0-0/*" ] + } context "when patching with an empty body" $ do it "makes no updates and returns 204 without return= and without ?select=" $ do - request methodPatch "/items" [] [json| {} |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items" + [] + [json| {} |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } - request methodPatch "/items" [] [json| [] |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items" + [] + [json| [] |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } - request methodPatch "/items" [] [json| [{}] |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items" + [] + [json| [{}] |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } it "makes no updates and returns 204 without return= and with ?select=" $ do - request methodPatch "/items?select=id" [] [json| {} |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items?select=id" + [] + [json| {} |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } - request methodPatch "/items?select=id" [] [json| [] |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items?select=id" + [] + [json| [] |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } - request methodPatch "/items?select=id" [] [json| [{}] |] - `shouldRespondWith` "" - { - matchStatus = 204, - matchHeaders = ["Content-Range" <:> "*/*"] - } + request methodPatch "/items?select=id" + [] + [json| [{}] |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [ matchHeaderAbsent hContentType + , "Content-Range" <:> "*/*" ] + } it "makes no updates and returns 200 with return=rep and without ?select=" $ request methodPatch "/items" [("Prefer", "return=representation")] [json| {} |] @@ -339,11 +368,21 @@ spec = do context "table with limited privileges" $ do it "succeeds updating row and gives a 204 when using return=minimal" $ - request methodPatch "/app_users?id=eq.1" [("Prefer", "return=minimal")] - [json| { "password": "passxyz" } |] - `shouldRespondWith` 204 + request methodPatch "/app_users?id=eq.1" + [("Prefer", "return=minimal")] + [json| { "password": "passxyz" } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } it "can update without return=minimal and no explicit select" $ - request methodPatch "/app_users?id=eq.1" [] - [json| { "password": "passabc" } |] - `shouldRespondWith` 204 + request methodPatch "/app_users?id=eq.1" + [] + [json| { "password": "passabc" } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } diff --git a/test/Feature/UpsertSpec.hs b/test/Feature/UpsertSpec.hs index e1962f4c2..e33f24f1a 100644 --- a/test/Feature/UpsertSpec.hs +++ b/test/Feature/UpsertSpec.hs @@ -422,6 +422,12 @@ spec actualPgVersion = } it "works with PUT" $ do - put "/UnitTest?idUnitTest=eq.1" [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] `shouldRespondWith` 204 + put "/UnitTest?idUnitTest=eq.1" + [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = [matchHeaderAbsent hContentType] + } get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith` [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|]