fix: Return 204 No Content without Content-Type for PUT

This commit is contained in:
Wolfgang Walther
2021-12-01 09:19:47 +01:00
committed by Wolfgang Walther
parent 1cb00d3c62
commit 0e20b47875
15 changed files with 373 additions and 193 deletions
+1
View File
@@ -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
+1 -1
View File
@@ -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
+45 -20
View File
@@ -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]
}
+8 -2
View File
@@ -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
+103 -52
View File
@@ -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" <:> "*/*" ]
}
+7 -2
View File
@@ -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"
+16 -6
View File
@@ -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" ]
}
+27 -17
View File
@@ -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
+7 -4
View File
@@ -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"
+22 -10
View File
@@ -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"
+15 -10
View File
@@ -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
+7 -4
View File
@@ -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
+6 -2
View File
@@ -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]
}
+101 -62
View File
@@ -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]
}
+7 -1
View File
@@ -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" } ]|]