Ignore the Range header when the method is different than GET

fix: bug when using Range header on PATCH/DELETE
  - Fix the "message": "syntax error at or near \"RETURNING\"" error
  - Fix doing a limited update/delete when an order query parameter was present

breaking: The Range header is now only considered on GET requests and is ignored for any other method
  - Other methods should use the `limit/offset` query parameters for sub-ranges
  - PUT requests no longer return an error when this header is present
This commit is contained in:
Laurence Isla
2023-04-03 10:40:25 -05:00
committed by GitHub
parent 032f07f3f0
commit 1aed55be68
11 changed files with 291 additions and 109 deletions
+24 -33
View File
@@ -1,7 +1,5 @@
module Feature.Query.RangeSpec where
import qualified Data.ByteString.Lazy as BL
import Network.Wai (Application)
import Network.Wai.Test (SResponse (simpleHeaders, simpleStatus))
@@ -13,36 +11,29 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
defaultRange :: BL.ByteString
defaultRange = [json| { "min": 0, "max": 15 } |]
emptyRange :: BL.ByteString
emptyRange = [json| { "min": 2, "max": 2 } |]
spec :: SpecWith ((), Application)
spec = do
describe "POST /rpc/getitemrange" $ do
describe "GET /rpc/getitemrange" $ do
context "without range headers" $ do
context "with response under server size limit" $
it "returns whole range with status 200" $
post "/rpc/getitemrange" defaultRange `shouldRespondWith` 200
get "/rpc/getitemrange?min=0&max=15" `shouldRespondWith` 200
context "when I don't want the count" $ do
it "returns range Content-Range with */* for empty range" $
request methodPost "/rpc/getitemrange" [] emptyRange
get "/rpc/getitemrange?min=2&max=2"
`shouldRespondWith` [json| [] |] {matchHeaders = ["Content-Range" <:> "*/*"]}
it "returns range Content-Range with range/*" $
post "/rpc/getitemrange?order=id"
defaultRange
get "/rpc/getitemrange?order=id&min=0&max=15"
`shouldRespondWith`
[json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |]
{ matchHeaders = ["Content-Range" <:> "0-14/*"] }
context "of invalid range" $ do
it "refuses a range with nonzero start when there are no items" $
request methodPost "/rpc/getitemrange?offset=1"
[("Prefer", "count=exact")] emptyRange
request methodGet "/rpc/getitemrange?offset=1&min=2&max=2"
[("Prefer", "count=exact")] mempty
`shouldRespondWith`
[json| {
"message":"Requested range not satisfiable",
@@ -55,8 +46,8 @@ spec = do
}
it "refuses a range requesting start past last item" $
request methodPost "/rpc/getitemrange?offset=100"
[("Prefer", "count=exact")] defaultRange
request methodGet "/rpc/getitemrange?offset=100&min=0&max=15"
[("Prefer", "count=exact")] mempty
`shouldRespondWith`
[json| {
"message":"Requested range not satisfiable",
@@ -71,37 +62,37 @@ spec = do
context "with range headers" $ do
context "of acceptable range" $ do
it "succeeds with partial content" $ do
r <- request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 0 1) defaultRange
r <- request methodGet "/rpc/getitemrange?min=0&max=15"
(rangeHdrs $ ByteRangeFromTo 0 1) mempty
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-1/*"
simpleStatus r `shouldBe` ok200
it "understands open-ended ranges" $
request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFrom 0) defaultRange
request methodGet "/rpc/getitemrange?min=0&max=15"
(rangeHdrs $ ByteRangeFrom 0) mempty
`shouldRespondWith` 200
it "returns an empty body when there are no results" $
request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 0 1) emptyRange
request methodGet "/rpc/getitemrange?min=2&max=2"
(rangeHdrs $ ByteRangeFromTo 0 1) mempty
`shouldRespondWith` "[]"
{ matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "allows one-item requests" $ do
r <- request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 0 0) defaultRange
r <- request methodGet "/rpc/getitemrange?min=0&max=15"
(rangeHdrs $ ByteRangeFromTo 0 0) mempty
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-0/*"
simpleStatus r `shouldBe` ok200
it "handles ranges beyond collection length via truncation" $ do
r <- request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 10 100) defaultRange
r <- request methodGet "/rpc/getitemrange?min=0&max=15"
(rangeHdrs $ ByteRangeFromTo 10 100) mempty
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "10-14/*"
@@ -109,8 +100,8 @@ spec = do
context "of invalid range" $ do
it "fails with 416 for offside range" $
request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 1 0) emptyRange
request methodGet "/rpc/getitemrange?min=2&max=2"
(rangeHdrs $ ByteRangeFromTo 1 0) mempty
`shouldRespondWith`
[json| {
"message":"Requested range not satisfiable",
@@ -121,8 +112,8 @@ spec = do
{ matchStatus = 416 }
it "refuses a range with nonzero start when there are no items" $
request methodPost "/rpc/getitemrange"
(rangeHdrsWithCount $ ByteRangeFromTo 1 2) emptyRange
request methodGet "/rpc/getitemrange?min=2&max=2"
(rangeHdrsWithCount $ ByteRangeFromTo 1 2) mempty
`shouldRespondWith`
[json| {
"message":"Requested range not satisfiable",
@@ -135,8 +126,8 @@ spec = do
}
it "refuses a range requesting start past last item" $
request methodPost "/rpc/getitemrange"
(rangeHdrsWithCount $ ByteRangeFromTo 100 199) defaultRange
request methodGet "/rpc/getitemrange?min=0&max=15"
(rangeHdrsWithCount $ ByteRangeFromTo 100 199) mempty
`shouldRespondWith`
[json| {
"message":"Requested range not satisfiable",