No count by default (#700)

* WIP: disable counting total table size by default

* Remove commented tests since PUT is no longer supported

* Fix tests for invalid range which require count

* Do not report count in PATCH response if not asked

* Return count of deleted items only if asked

* Return count for bulk insert when requested

* Changelog entry
This commit is contained in:
Joe Nelson
2016-09-04 10:31:24 -07:00
committed by GitHub
parent 7278507c42
commit af75988dd4
9 changed files with 100 additions and 150 deletions
+1
View File
@@ -21,6 +21,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Use HTTP 400 for raise\_exception - @begriffs
- Remove non-OpenAPI schema description - @begriffs
- Use comma rather than semicolon to separate Prefer header values - @begriffs
- Omit total query count by default - @begriffs
## [0.3.2.0] - 2016-06-10
+1 -1
View File
@@ -153,7 +153,7 @@ userApiRequest schema req reqBody =
, iPayload = relevantPayload
, iPreferRepresentation = representation
, iPreferSingular = singular
, iPreferCount = not $ singular || hasPrefer "count=none"
, iPreferCount = not singular && hasPrefer "count=exact"
, iFilters = [ (toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, k /= "select", k /= "offset", not (endingIn ["order", "limit"] k) ]
, iSelect = toS $ fromMaybe "*" $ fromMaybe (Just "*") $ lookup "select" qParams
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
+17 -8
View File
@@ -141,13 +141,20 @@ app dbStructure conf apiRequest =
let stm = createWriteStatement qi sq mq isSingle (iPreferRepresentation apiRequest) pKeys (contentType == CTTextCSV) payload
row <- H.query uniform stm
let (_, _, fs, body) = extractQueryResult row
header =
if null fs then []
else [(hLocation, "/" <> toS table <> renderLocationFields fs)]
headers = catMaybes [
if null fs
then Nothing
else Just (hLocation, "/" <> toS table <> renderLocationFields fs)
, if iPreferRepresentation apiRequest == Full
then Just $ ctToHeader contentType
else Nothing
, Just . contentRangeH 1 0 $
toInteger <$> if shouldCount then Just (V.length rows) else Nothing
]
return $ if iPreferRepresentation apiRequest == Full
then responseLBS status201 (ctToHeader contentType : header) (toS body)
else responseLBS status201 header ""
return . responseLBS status201 headers $
if iPreferRepresentation apiRequest == Full
then toS body else ""
(ActionUpdate, TargetIdent qi, Just payload@(PayloadJSON uniform)) ->
serves [CTApplicationJSON, CTTextCSV] (iAccepts apiRequest) $ \contentType ->
@@ -165,7 +172,8 @@ app dbStructure conf apiRequest =
'plurality=singular specified, but more than one object would be updated';
END $$;
|]
let r = contentRangeH 0 (toInteger $ queryTotal-1) (toInteger <$> Just queryTotal)
let r = contentRangeH 0 (toInteger $ queryTotal-1)
(toInteger <$> if shouldCount then Just queryTotal else Nothing)
s = case () of _ | queryTotal == 0 -> status404
| iPreferRepresentation apiRequest == Full -> status200
| otherwise -> status204
@@ -183,7 +191,8 @@ app dbStructure conf apiRequest =
stm = createWriteStatement qi sq mq False (iPreferRepresentation apiRequest) [] (contentType == CTTextCSV) fakeload
row <- H.query emptyUniform stm
let (_, queryTotal, _, body) = extractQueryResult row
r = contentRangeH 1 0 (toInteger <$> Just queryTotal)
r = contentRangeH 1 0 $
toInteger <$> if shouldCount then Just queryTotal else Nothing
return $ if queryTotal == 0
then notFound
else if iPreferRepresentation apiRequest == Full
+6 -6
View File
@@ -16,11 +16,11 @@ spec =
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 204
, matchHeaders = ["Content-Range" <:> "*/1"]
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "returns the deleted item" $
request methodDelete "/items?id=eq.2" [("Prefer", "return=representation")] ""
it "returns the deleted item and count if requested" $
request methodDelete "/items?id=eq.2" [("Prefer", "return=representation"), ("Prefer", "count=exact")] ""
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":2}]|]
, matchStatus = 200
@@ -31,14 +31,14 @@ spec =
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":2,"name":"Two"}]|]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/1"]
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "can embed (parent) entities" $
request methodDelete "/tasks?id=eq.8&select=id,name,project{id}" [("Prefer", "return=representation")] ""
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":8,"name":"Code OSX","project":{"id":4}}]|]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/1"]
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "actually clears items ouf the db" $ do
@@ -47,7 +47,7 @@ spec =
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":15}]|]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/1"]
, matchHeaders = ["Content-Range" <:> "0-0/*"]
}
context "known route, unknown record" $
+9 -81
View File
@@ -48,11 +48,14 @@ spec = do
}
it "includes related data after insert" $
request methodPost "/projects?select=id,name,clients{id,name}" [("Prefer", "return=representation")]
request methodPost "/projects?select=id,name,clients{id,name}"
[("Prefer", "return=representation"), ("Prefer", "count=exact")]
[str|{"id":6,"name":"New Project","client_id":2}|] `shouldRespondWith` ResponseMatcher {
matchBody = Just [str|{"id":6,"name":"New Project","clients":{"id":2,"name":"Apple"}}|]
, matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", "Location" <:> "/projects?id=eq.6"]
, matchHeaders = [ "Content-Type" <:> "application/json; charset=utf-8"
, "Location" <:> "/projects?id=eq.6"
, "Content-Range" <:> "*/1" ]
}
context "from an html form" $
@@ -258,81 +261,6 @@ spec = do
liftIO $ simpleBody r `shouldBe` "["<>payload<>"]"
describe "Putting record" $ do
context "to unknown uri" $
it "gives a 404" $ do
pendingWith "Decide on PUT usefullness"
request methodPut "/fake" []
[json| { "real": false } |]
`shouldRespondWith` 404
context "to a known uri" $ do
context "without a fully-specified primary key" $
it "is not an allowed operation" $ do
pendingWith "Decide on PUT usefullness"
request methodPut "/compound_pk?k1=eq.12" []
[json| { "k1":12, "k2":42 } |]
`shouldRespondWith` 405
context "with a fully-specified primary key" $ do
context "not specifying every column in the table" $
it "is rejected for lack of idempotence" $ do
pendingWith "Decide on PUT usefullness"
request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []
[json| { "k1":12, "k2":42 } |]
`shouldRespondWith` 400
context "specifying every column in the table" $ do
it "can create a new record" $ do
pendingWith "Decide on PUT usefullness"
p <- request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []
[json| { "k1":12, "k2":42, "extra":3 } |]
liftIO $ do
simpleBody p `shouldBe` ""
simpleStatus p `shouldBe` status204
r <- get "/compound_pk?k1=eq.12&k2=eq.42"
let rows = fromJust (JSON.decode $ simpleBody r :: Maybe [CompoundPK])
liftIO $ do
length rows `shouldBe` 1
let record = head rows
compoundK1 record `shouldBe` 12
compoundK2 record `shouldBe` "42"
compoundExtra record `shouldBe` Just 3
it "can update an existing record" $ do
pendingWith "Decide on PUT usefullness"
_ <- request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []
[json| { "k1":12, "k2":42, "extra":4 } |]
_ <- request methodPut "/compound_pk?k1=eq.12&k2=eq.42" []
[json| { "k1":12, "k2":42, "extra":5 } |]
r <- get "/compound_pk?k1=eq.12&k2=eq.42"
let rows = fromJust (JSON.decode $ simpleBody r :: Maybe [CompoundPK])
liftIO $ do
length rows `shouldBe` 1
let record = head rows
compoundExtra record `shouldBe` Just 5
context "with an auto-incrementing primary key"$
it "succeeds with 204" $ do
pendingWith "Decide on PUT usefullness"
request methodPut "/auto_incrementing_pk?id=eq.1" []
[json| {
"id":1,
"nullable_string":"hi",
"non_nullable_string":"bye",
"inserted_at": "2020-11-11"
} |]
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing,
matchStatus = 204,
matchHeaders = []
}
describe "Patching record" $ do
context "to unknown uri" $
@@ -351,19 +279,19 @@ spec = do
it "can update a single item" $ do
g <- get "/items?id=eq.42"
liftIO $ simpleHeaders g
`shouldSatisfy` matchHeader "Content-Range" "\\*/0"
`shouldSatisfy` matchHeader "Content-Range" "\\*/\\*"
p <- request methodPatch "/items?id=eq.2" [] [json| { "id":42 } |]
pure p `shouldRespondWith` ResponseMatcher {
matchBody = Nothing,
matchStatus = 204,
matchHeaders = ["Content-Range" <:> "0-0/1"]
matchHeaders = ["Content-Range" <:> "0-0/*"]
}
liftIO $
lookup hContentType (simpleHeaders p) `shouldBe` Nothing
g' <- get "/items?id=eq.42"
liftIO $ simpleHeaders g'
`shouldSatisfy` matchHeader "Content-Range" "0-0/1"
`shouldSatisfy` matchHeader "Content-Range" "0-0/\\*"
it "can update multiple items" $ do
replicateM_ 10 $ post "/auto_incrementing_pk"
@@ -375,7 +303,7 @@ spec = do
[json| { non_nullable_string: "c" } |]
g <- get "/auto_incrementing_pk?non_nullable_string=eq.c"
liftIO $ simpleHeaders g
`shouldSatisfy` matchHeader "Content-Range" "0-9/10"
`shouldSatisfy` matchHeader "Content-Range" "0-9/\\*"
it "can set a column to NULL" $ do
_ <- post "/no_pk" [json| { a: "keepme", b: "nullme" } |]
+8 -8
View File
@@ -16,8 +16,8 @@ spec =
get "/items"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":2}] |]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-1/15"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "respects additional client limiting" $ do
@@ -25,22 +25,22 @@ spec =
(rangeHdrs $ ByteRangeFromTo 0 0) ""
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-0/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "0-0/*"
simpleStatus r `shouldBe` ok200
it "limit works on all levels" $
get "/users?select=id,tasks{id}&order=id.asc&tasks.order=id.asc"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":5},{"id":6}]}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-1/3"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "limit is not applied to parent embeds" $
get "/tasks?select=id,project{id}&id=gt.5"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-1/3"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
+25 -15
View File
@@ -27,7 +27,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/1"]
, matchHeaders = ["Content-Range" <:> "0-0/*"]
}
it "matches with equality using not operator" $
@@ -35,7 +35,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-13/14"]
, matchHeaders = ["Content-Range" <:> "0-13/*"]
}
it "matches with more than one condition using not operator" $
@@ -46,13 +46,13 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":14},{"id":15}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
get "/items?id=not.gt.2&order=id.asc"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":2}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "matches items IN" $
@@ -60,7 +60,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "matches items NOT IN" $
@@ -68,7 +68,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "matches items NOT IN using not operator" $
@@ -76,7 +76,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "matches nulls using not operator" $
@@ -319,14 +319,14 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":1},{"id":2}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "by a column desc" $
get "/items?id=lte.2&order=id.desc"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":2},{"id":1}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/2"]
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "by a column asc with nulls last" $
@@ -336,7 +336,7 @@ spec = do
{"a":"2","b":"0"},
{"a":null,"b":null}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "by a column desc with nulls first" $
@@ -346,7 +346,7 @@ spec = do
{"a":"2","b":"0"},
{"a":"1","b":"0"}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "by a column desc with nulls last" $
@@ -356,7 +356,7 @@ spec = do
{"a":"1","b":"0"},
{"a":null,"b":null}] |]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/3"]
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "without other constraints" $
@@ -466,7 +466,17 @@ spec = do
(rangeHdrs (ByteRangeFromTo 0 0)) [json| { "min": 2, "max": 4 } |]
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":3}] |]
, matchStatus = 206
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/*"]
}
it "includes total count if requested" $
request methodPost "/rpc/getitemrange"
(rangeHdrsWithCount (ByteRangeFromTo 0 0))
[json| { "min": 2, "max": 4 } |]
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [{"id":3}] |]
, matchStatus = 206 -- it now knows the response is partial
, matchHeaders = ["Content-Range" <:> "0-0/2"]
}
@@ -488,8 +498,8 @@ spec = do
post "/rpc/getallprojects?id=gt.1&id=lt.5&select=id?limit=2&offset=1" [json| {} |]
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json|[{"id":3},{"id":4}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "1-2/3"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "1-2/*"]
}
+30 -31
View File
@@ -28,8 +28,7 @@ spec = do
context "when I don't want the count" $ do
it "returns range Content-Range with */* for empty range" $
request methodPost "/rpc/getitemrange"
[("Prefer", "count=none")] emptyRange
request methodPost "/rpc/getitemrange" [] emptyRange
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| [] |]
, matchStatus = 200
@@ -37,8 +36,7 @@ spec = do
}
it "returns range Content-Range with range/*" $
request methodPost "/rpc/getitemrange"
[("Prefer", "count=none")] defaultRange
request methodPost "/rpc/getitemrange" [] defaultRange
`shouldRespondWith` ResponseMatcher {
matchBody = Just [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}] |]
, matchStatus = 200
@@ -53,8 +51,8 @@ spec = do
(rangeHdrs $ ByteRangeFromTo 0 1) defaultRange
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-1/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "0-1/*"
simpleStatus r `shouldBe` ok200
it "understands open-ended ranges" $
request methodPost "/rpc/getitemrange"
@@ -67,7 +65,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[]"
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/0"]
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "allows one-item requests" $ do
@@ -75,16 +73,16 @@ spec = do
(rangeHdrs $ ByteRangeFromTo 0 0) defaultRange
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-0/15"
simpleStatus r `shouldBe` partialContent206
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
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "10-14/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "10-14/*"
simpleStatus r `shouldBe` ok200
context "of invalid range" $ do
it "fails with 416 for offside range" $
@@ -94,7 +92,7 @@ spec = do
it "refuses a range with nonzero start when there are no items" $
request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 1 2) emptyRange
(rangeHdrsWithCount $ ByteRangeFromTo 1 2) emptyRange
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 416
@@ -103,12 +101,13 @@ spec = do
it "refuses a range requesting start past last item" $
request methodPost "/rpc/getitemrange"
(rangeHdrs $ ByteRangeFromTo 100 199) defaultRange
(rangeHdrsWithCount $ ByteRangeFromTo 100 199) defaultRange
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 416
, matchHeaders = ["Content-Range" <:> "*/15"]
}
describe "GET /items" $ do
context "without range headers" $ do
context "with response under server size limit" $
@@ -149,30 +148,30 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"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}]|]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-14/15"]
, matchHeaders = ["Content-Range" <:> "0-14/*"]
}
it "top level limit with parameter" $
get "/items?select=id&order=id.asc&limit=3"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":1},{"id":2},{"id":3}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-2/15"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-2/*"]
}
it "headers override get parameters" $
request methodGet "/items?select=id&order=id.asc&limit=3"
(rangeHdrs $ ByteRangeFromTo 0 1) ""
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":1},{"id":2}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-1/15"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-1/*"]
}
it "limit works on all levels" $
get "/clients?select=id,projects{id,tasks{id}}&order=id.asc&limit=1&projects.order=id.asc&projects.limit=1&projects.tasks.order=id.asc&projects.tasks.limit=2"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]}]}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "0-0/2"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/*"]
}
it "fails on offset specified below level 1" $
@@ -183,8 +182,8 @@ spec = do
get "/items?select=id&order=id.asc&limit=3&offset=2"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [str|[{"id":3},{"id":4},{"id":5}]|]
, matchStatus = 206
, matchHeaders = ["Content-Range" <:> "2-4/15"]
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "2-4/*"]
}
context "with range headers" $ do
@@ -195,8 +194,8 @@ spec = do
(rangeHdrs $ ByteRangeFromTo 0 1) ""
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-1/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "0-1/*"
simpleStatus r `shouldBe` ok200
it "understands open-ended ranges" $
request methodGet "/items"
@@ -209,7 +208,7 @@ spec = do
`shouldRespondWith` ResponseMatcher {
matchBody = Just "[]"
, matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/0"]
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "allows one-item requests" $ do
@@ -217,16 +216,16 @@ spec = do
(rangeHdrs $ ByteRangeFromTo 0 0) ""
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "0-0/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "0-0/*"
simpleStatus r `shouldBe` ok200
it "handles ranges beyond collection length via truncation" $ do
r <- request methodGet "/items"
(rangeHdrs $ ByteRangeFromTo 10 100) ""
liftIO $ do
simpleHeaders r `shouldSatisfy`
matchHeader "Content-Range" "10-14/15"
simpleStatus r `shouldBe` partialContent206
matchHeader "Content-Range" "10-14/*"
simpleStatus r `shouldBe` ok200
context "of invalid range" $ do
it "fails with 416 for offside range" $
@@ -236,7 +235,7 @@ spec = do
it "refuses a range with nonzero start when there are no items" $
request methodGet "/menagerie"
(rangeHdrs $ ByteRangeFromTo 1 2) ""
(rangeHdrsWithCount $ ByteRangeFromTo 1 2) ""
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 416
@@ -245,7 +244,7 @@ spec = do
it "refuses a range requesting start past last item" $
request methodGet "/items"
(rangeHdrs $ ByteRangeFromTo 100 199) ""
(rangeHdrsWithCount $ ByteRangeFromTo 100 199) ""
`shouldRespondWith` ResponseMatcher {
matchBody = Nothing
, matchStatus = 416
+3
View File
@@ -84,6 +84,9 @@ loadFixture name =
rangeHdrs :: ByteRange -> [Header]
rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)]
rangeHdrsWithCount :: ByteRange -> [Header]
rangeHdrsWithCount r = ("Prefer", "count=exact") : rangeHdrs r
acceptHdrs :: BS.ByteString -> [Header]
acceptHdrs mime = [(hAccept, mime)]