feat: add Preference-Applied header in response for Prefer: return=representation/minimal/headers-only

This commit is contained in:
Taimoor Zaeem
2023-08-05 02:21:50 -05:00
committed by Steve Chavez
parent aa53623aac
commit d490bf09fd
11 changed files with 261 additions and 142 deletions
+1
View File
@@ -23,6 +23,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #2647, Allow to verify the PostgREST version in SQL: `select distinct application_name from pg_stat_activity`. - @laurenceisla
- #2856, Add the `--version` CLI option that prints the version information - @laurenceisla
- #1655, Improve `details` field of the singular error response - @taimoorzaeem
- #740, Add `Preference-Applied` in response for `Prefer: return=representation/headers-only/minimal` - @taimoorzaeem
### Fixed
+12 -10
View File
@@ -41,7 +41,7 @@ import Protolude
data Preferences
= Preferences
{ preferResolution :: Maybe PreferResolution
, preferRepresentation :: PreferRepresentation
, preferRepresentation :: Maybe PreferRepresentation
, preferParameters :: Maybe PreferParameters
, preferCount :: Maybe PreferCount
, preferTransaction :: Maybe PreferTransaction
@@ -56,7 +56,7 @@ data Preferences
-- >>> pPrint $ fromHeaders [("Prefer", "resolution=ignore-duplicates, count=exact")]
-- Preferences
-- { preferResolution = Just IgnoreDuplicates
-- , preferRepresentation = None
-- , preferRepresentation = Nothing
-- , preferParameters = Nothing
-- , preferCount = Just ExactCount
-- , preferTransaction = Nothing
@@ -68,7 +68,7 @@ data Preferences
-- >>> pPrint $ fromHeaders [("Prefer", "resolution=ignore-duplicates"), ("Prefer", "count=exact"), ("Prefer", "missing=null")]
-- Preferences
-- { preferResolution = Just IgnoreDuplicates
-- , preferRepresentation = None
-- , preferRepresentation = Nothing
-- , preferParameters = Nothing
-- , preferCount = Just ExactCount
-- , preferTransaction = Nothing
@@ -100,7 +100,7 @@ data Preferences
-- >>> pPrint $ fromHeaders [("prefer", "count=exact, tx=commit ,return=representation , missing=default")]
-- Preferences
-- { preferResolution = Nothing
-- , preferRepresentation = Full
-- , preferRepresentation = Just Full
-- , preferParameters = Nothing
-- , preferCount = Just ExactCount
-- , preferTransaction = Just Commit
@@ -110,12 +110,12 @@ data Preferences
fromHeaders :: [HTTP.Header] -> Preferences
fromHeaders headers =
Preferences
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
, preferRepresentation = fromMaybe None $ parsePrefs [Full, None, HeadersOnly]
, preferParameters = parsePrefs [SingleObject]
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
, preferTransaction = parsePrefs [Commit, Rollback]
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
{ preferResolution = parsePrefs [MergeDuplicates, IgnoreDuplicates]
, preferRepresentation = parsePrefs [Full, None, HeadersOnly]
, preferParameters = parsePrefs [SingleObject]
, preferCount = parsePrefs [ExactCount, PlannedCount, EstimatedCount]
, preferTransaction = parsePrefs [Commit, Rollback]
, preferMissing = parsePrefs [ApplyDefaults, ApplyNulls]
}
where
prefHeaders = filter ((==) HTTP.hPrefer . fst) headers
@@ -168,6 +168,8 @@ data PreferRepresentation
| None -- ^ Return nothing from the mutated data.
deriving Eq
instance ToAppliedHeader PreferRepresentation
instance ToHeaderValue PreferRepresentation where
toHeaderValue Full = "return=representation"
toHeaderValue None = "return=minimal"
+2 -2
View File
@@ -722,7 +722,7 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
confCols = fromMaybe pkCols qsOnConflict
QueryParams.QueryParams{..} = iQueryParams
returnings =
if preferRepresentation == None
if preferRepresentation == Just None || isNothing preferRepresentation
then []
else inferColsEmbedNeeds readReq pkCols
tbl = HM.lookup qi dbTables
@@ -866,7 +866,7 @@ mediaToAggregate mt binField apiReq@ApiRequest{iAction=act, iPreferences=Prefere
MTPlan media _ _ -> mediaToAggregate media binField apiReq
where
noAgg = case act of
ActionMutate _ -> rep == HeadersOnly || rep == None
ActionMutate _ -> rep == Just HeadersOnly || rep == Just None || isNothing rep
ActionRead _isHead -> _isHead -- no need for an aggregate on HEAD https://github.com/PostgREST/postgrest/issues/2849
ActionInvoke invMethod -> invMethod == InvHead
_ -> False
+2 -2
View File
@@ -54,7 +54,7 @@ data ResultSet
prepareWrite :: SQL.Snippet -> SQL.Snippet -> Bool -> MediaType -> ResultAggregate ->
PreferRepresentation -> [Text] -> Bool -> SQL.Statement () ResultSet
Maybe PreferRepresentation -> [Text] -> Bool -> SQL.Statement () ResultSet
prepareWrite selectQuery mutateQuery isInsert mt rAgg rep pKeys =
SQL.dynamicallyParameterized (mtSnippet mt snippet) decodeIt
where
@@ -70,7 +70,7 @@ prepareWrite selectQuery mutateQuery isInsert mt rAgg rep pKeys =
"FROM (" <> selectF <> ") _postgrest_t"
locF =
if isInsert && rep == HeadersOnly
if isInsert && rep == Just HeadersOnly
then
"CASE WHEN pg_catalog.count(_postgrest_t) = 1 " <>
"THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ") " <>
+58 -19
View File
@@ -1,3 +1,7 @@
{- |
Module : PostgREST.Response
Description : Generate HTTP Response
-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
module PostgREST.Response
@@ -14,6 +18,8 @@ module PostgREST.Response
, addRetryHint
, isServiceUnavailable
, optionalRollback
, concatPrefAppsHeaders
, addPrefToHeaders
, traceHeaderMiddleware
) where
@@ -60,6 +66,7 @@ import qualified PostgREST.SchemaCache.Routine as Routine
import Protolude hiding (Handler, toS)
import Protolude.Conv (toS)
readResponse :: Bool -> QualifiedIdentifier -> ApiRequest -> ResultSet -> Wai.Response
readResponse headersOnly identifier ctxApiRequest@ApiRequest{..} resultSet = case resultSet of
RSStandard{..} -> do
@@ -111,14 +118,17 @@ createResponse QualifiedIdentifier{..} MutateReadPlan{mrMutatePlan} ctxApiReques
, toAppliedHeader <$> preferMissing
]
if preferRepresentation == Full then
response HTTP.status201 (headers ++ contentTypeHeaders ctxApiRequest) (LBS.fromStrict rsBody)
else
response HTTP.status201 headers mempty
case preferRepresentation of
Just Full -> response HTTP.status201 (addPrefToHeaders headers Full ++ contentTypeHeaders ctxApiRequest) (LBS.fromStrict rsBody)
Just None -> response HTTP.status201 (addPrefToHeaders headers None) mempty
Just HeadersOnly -> response HTTP.status201 (addPrefToHeaders headers HeadersOnly) mempty
Nothing -> response HTTP.status201 headers mempty
RSPlan plan ->
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
updateResponse :: ApiRequest -> ResultSet -> Wai.Response
updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet = case resultSet of
RSStandard{..} -> do
@@ -129,12 +139,11 @@ updateResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet
if shouldCount preferCount then Just rsQueryTotal else Nothing
headers = catMaybes [contentRangeHeader, toAppliedHeader <$> preferMissing]
if preferRepresentation == Full then
response HTTP.status200
(headers ++ contentTypeHeaders ctxApiRequest)
case preferRepresentation of
Just Full -> response HTTP.status200 (addPrefToHeaders headers Full ++ contentTypeHeaders ctxApiRequest)
(LBS.fromStrict rsBody)
else
response HTTP.status204 headers mempty
Just None -> response HTTP.status204 (addPrefToHeaders headers None) mempty
_ -> response HTTP.status204 headers mempty
RSPlan plan ->
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
@@ -145,10 +154,10 @@ singleUpsertResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resu
let
response = gucResponse rsGucStatus rsGucHeaders
if preferRepresentation == Full then
response HTTP.status200 (contentTypeHeaders ctxApiRequest) (LBS.fromStrict rsBody)
else
response HTTP.status204 [] mempty
case preferRepresentation of
Just Full -> response HTTP.status200 (contentTypeHeaders ctxApiRequest ++ [toAppliedHeader Full]) (LBS.fromStrict rsBody)
Just None -> response HTTP.status204 [toAppliedHeader None] mempty
_ -> response HTTP.status204 [] mempty
RSPlan plan ->
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
@@ -163,12 +172,11 @@ deleteResponse ctxApiRequest@ApiRequest{iPreferences=Preferences{..}} resultSet
if shouldCount preferCount then Just rsQueryTotal else Nothing
headers = [contentRangeHeader]
if preferRepresentation == Full then
response HTTP.status200
(headers ++ contentTypeHeaders ctxApiRequest)
case preferRepresentation of
Just Full -> response HTTP.status200 (addPrefToHeaders headers Full ++ contentTypeHeaders ctxApiRequest)
(LBS.fromStrict rsBody)
else
response HTTP.status204 headers mempty
Just None -> response HTTP.status204 (addPrefToHeaders headers None) mempty
_ -> response HTTP.status204 headers mempty
RSPlan plan ->
Wai.responseLBS HTTP.status200 (contentTypeHeaders ctxApiRequest) $ LBS.fromStrict plan
@@ -292,9 +300,40 @@ optionalRollback AppConfig{..} ApiRequest{iPreferences=Preferences{..}} resp = d
-- | Add headers not already included to allow the user to override them instead of duplicating them
addHeadersIfNotIncluded :: [HTTP.Header] -> [HTTP.Header] -> [HTTP.Header]
addHeadersIfNotIncluded newHeaders initialHeaders =
filter (\(nk, _) -> isNothing $ find (\(ik, _) -> ik == nk) initialHeaders) newHeaders ++
filter (\(nk, nv) -> isNothing $ find (\(ik, iv) -> ik == nk && nv == iv) initialHeaders) newHeaders ++
initialHeaders
-- | Filters out multiple Preference-Applied Headers from the list and concatenate them into a single Preference-Applied header:
--
-- >>> :{
-- concatPrefAppsHeaders
-- [("Content-Type","application/json")
-- , ("Preference-Applied","tx=commit")
-- , ("Preference-Applied","return=minimal")]
-- :}
-- [("Content-Type","application/json"),("Preference-Applied","tx=commit, return=minimal")]
concatPrefAppsHeaders :: [HTTP.Header] -> [HTTP.Header]
concatPrefAppsHeaders headers = otherHeaders ++ [(HTTP.hPreferenceApplied, combinedPrefApps)]
where
(prefApps, otherHeaders) = L.partition (\(k, _) -> k == HTTP.hPreferenceApplied) headers
prefAppsValues = [ v | (_,v) <- prefApps]
combinedPrefApps = BS.intercalate ", " prefAppsValues
-- | Given response headers and a preferRepresentation value, add
-- preferRepresentation to Preference-Applied
--
-- >>> :{
-- addPrefToHeaders
-- [("Content-Type", "application/json")
-- , ("Preference-Applied", "tx=commit")]
-- None
-- :}
-- [("Content-Type","application/json"),("Preference-Applied","tx=commit, return=minimal")]
addPrefToHeaders :: [HTTP.Header] -> PreferRepresentation -> [HTTP.Header]
addPrefToHeaders headers pref = concatPrefAppsHeaders (headers ++ [toAppliedHeader pref])
traceHeaderMiddleware :: AppConfig -> Wai.Middleware
traceHeaderMiddleware AppConfig{configServerTraceHeader} app req respond =
case configServerTraceHeader of
+1
View File
@@ -20,4 +20,5 @@ main =
, "src/PostgREST/MediaType.hs"
, "src/PostgREST/Config.hs"
, "src/PostgREST/Plan.hs"
, "src/PostgREST/Response.hs"
]
+6 -3
View File
@@ -37,7 +37,8 @@ spec =
request methodDelete "/items?id=eq.2" [("Prefer", "return=representation"), ("Prefer", "count=exact")] ""
`shouldRespondWith` [json|[{"id":2}]|]
{ matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/1"]
, matchHeaders = ["Content-Range" <:> "*/1"
, "Preference-Applied" <:> "return=representation"]
}
it "ignores ?select= when return not set or return=minimal" $ do
@@ -57,7 +58,8 @@ spec =
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=minimal"]
}
it "returns the deleted item and shapes the response" $
@@ -137,7 +139,8 @@ spec =
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [matchHeaderAbsent hContentType]
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal" ]
}
it "suceeds deleting the row with no explicit select by default" $
+36 -20
View File
@@ -43,7 +43,8 @@ spec actualPgVersion = do
, "enum": "foo"
}] |] `shouldRespondWith` [json|[{"integer":14,"varchar":"testing!"}]|]
{ matchStatus = 201
, matchHeaders = [matchContentTypeJson]
, matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"]
}
it "ignores &select when return not set or using return=minimal" $ do
@@ -69,7 +70,8 @@ spec actualPgVersion = do
`shouldRespondWith`
""
{ matchStatus = 201
, matchHeaders = [matchHeaderAbsent hContentType]
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal"]
}
context "non uniform json array" $ do
@@ -99,7 +101,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchContentTypeJson
, matchHeaderAbsent hLocation
, "Content-Range" <:> "*/1" ]
, "Content-Range" <:> "*/1"
, "Preference-Applied" <:> "return=representation"]
}
it "can rename and cast the selected columns" $
@@ -110,7 +113,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchContentTypeJson
, matchHeaderAbsent hLocation
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=representation"]
}
it "should not throw and return location header when selecting without PK" $
@@ -120,7 +124,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchContentTypeJson
, matchHeaderAbsent hLocation
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=representation"]
}
context "requesting headers only representation" $ do
@@ -133,7 +138,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/projects?id=eq.11"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
when (actualPgVersion >= pgVersion110) $
@@ -146,7 +152,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/car_models?name=eq.Enzo&year=eq.2021"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
context "requesting no representation" $
@@ -193,7 +200,8 @@ spec actualPgVersion = do
""
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/auto_incrementing_pk?id=eq.2" ]
, "Location" <:> "/auto_incrementing_pk?id=eq.2"
, "Preference-Applied" <:> "return=headers-only"]
}
context "into a table with simple pk" $
@@ -227,7 +235,8 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{ "a":"bar", "b":"baz" }] |]
{ matchStatus = 201
, matchHeaders = [matchHeaderAbsent hLocation]
, matchHeaders = [matchHeaderAbsent hLocation
, "Preference-Applied" <:> "return=representation"]
}
it "returns empty array when no items inserted, and return=rep" $ do
@@ -485,7 +494,7 @@ spec actualPgVersion = do
{"id": 6, "name": "Sechs", "field-with_sep": 6, "settings":null,"arr_data":[1,2,3]}
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "inserts view default values(field-with_sep) when json keys are undefined" $
@@ -500,7 +509,7 @@ spec actualPgVersion = do
{"id": 8, "name": "Default", "field-with_sep": 1, "settings":null,"arr_data":null}
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "doesn't insert json duplicate keys(since it uses jsonb)" $
@@ -509,7 +518,7 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [ { "data": { "a": 2 }, "id": 3 } ] |]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
when (actualPgVersion >= pgVersion100) $
@@ -519,7 +528,7 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"data":{"foo": "bar"},"slug":"foo"}] |] -- id 1 was inserted here, we don't get it for idempotence in the tests
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
when (actualPgVersion >= pgVersion120) $
@@ -551,7 +560,7 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"id": 666, "name": "Lu"}] |]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "inserts json that has duplicate keys" $ do
@@ -719,7 +728,8 @@ spec actualPgVersion = do
`shouldRespondWith`
""
{ matchStatus = 201
, matchHeaders = [matchHeaderAbsent hContentType]
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal"]
}
describe "Inserting into VIEWs" $ do
@@ -742,7 +752,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/with_multiple_pks?pk1=eq.1&pk2=eq.2"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
context "requesting header only representation" $ do
@@ -754,7 +765,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/compound_pk_view?k1=eq.1&k2=eq.test"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
it "should not throw and return location header when a PK is null" $
@@ -765,9 +777,11 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/test_null_pk_competitors_sponsors?id=eq.1&sponsor_id=is.null"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
-- Data representations for payload parsing requires Postgres 10 or above.
when (actualPgVersion >= pgVersion100) $ do
describe "Data representations" $ do
@@ -782,7 +796,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/datarep_todos?id=eq.5"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
it "parses values in POST body and formats individually selected values in return=representation" $
@@ -836,7 +851,8 @@ spec actualPgVersion = do
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Location" <:> "/datarep_todos_computed?id=eq.5"
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=headers-only"]
}
it "parses values in POST body and formats individually selected values in return=representation" $
+4 -2
View File
@@ -61,7 +61,8 @@ spec =
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [matchHeaderAbsent hContentType]
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal"]
}
it "raises an error for multiple rows" $ do
@@ -133,7 +134,8 @@ spec =
""
{ matchStatus = 201
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "*/*" ]
, "Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=minimal"]
}
it "raises an error when attempting to create multiple entities" $ do
+99 -66
View File
@@ -75,7 +75,7 @@ spec actualPgVersion = do
`shouldRespondWith` "[]"
{
matchStatus = 200,
matchHeaders = []
matchHeaders = ["Preference-Applied" <:> "return=representation"]
}
it "returns status code 200 when no rows updated" $
@@ -88,7 +88,8 @@ spec actualPgVersion = do
[("Prefer", "return=representation")] [json| { "id":2 } |]
`shouldRespondWith` [json|[{"id":2}]|]
{ matchStatus = 200,
matchHeaders = ["Content-Range" <:> "0-0/*"]
matchHeaders = ["Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "can update multiple items" $ do
@@ -137,7 +138,9 @@ spec actualPgVersion = do
[json| { id: 100 } |]
`shouldRespondWith` [json| [{ id: 100 }] |]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson, "Content-Range" <:> "0-0/*"]
matchHeaders = [matchContentTypeJson
,"Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "returns empty array when no rows updated and return=rep" $
@@ -147,7 +150,7 @@ spec actualPgVersion = do
[json| { id: 100 } |]
`shouldRespondWith` "[]"
{ matchStatus = 200,
matchHeaders = []
matchHeaders = ["Preference-Applied" <:> "return=representation"]
}
context "with representation requested" $ do
@@ -159,7 +162,9 @@ spec actualPgVersion = do
[("Prefer", "return=representation")]
[json| { id: 99 } |]
`shouldRespondWith` [json| [{id:99}] |]
{ matchHeaders = [matchContentTypeJson] }
{ matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"]
}
-- put value back for other tests
void $ request methodPatch "/items?id=eq.99" [] [json| { "id":1 } |]
@@ -169,7 +174,9 @@ spec actualPgVersion = do
[("Prefer", "return=representation")]
[json| { id: 1 } |]
`shouldRespondWith` [json| [{ id: 1, always_true: true }] |]
{ matchHeaders = [matchContentTypeJson] }
{ matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"]
}
it "can select overloaded computed columns" $ do
request methodPatch
@@ -177,22 +184,28 @@ spec actualPgVersion = do
[("Prefer", "return=representation")]
[json| { id: 1 } |]
`shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |]
{ matchHeaders = [matchContentTypeJson] }
{ matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"]
}
request methodPatch
"/items2?id=eq.1&select=id,computed_overload"
[("Prefer", "return=representation")]
[json| { id: 1 } |]
`shouldRespondWith` [json| [{ id: 1, computed_overload: true }] |]
{ matchHeaders = [matchContentTypeJson] }
{ matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"]
}
it "ignores ?select= when return not set or return=minimal" $ do
request methodPatch "/items?id=eq.1&select=id"
[] [json| { id:1 } |]
[("Prefer", "return=minimal")]
[json| { id:1 } |]
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "0-0/*" ]
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=minimal"]
}
request methodPatch "/items?id=eq.1&select=id"
[("Prefer", "return=minimal")]
@@ -201,7 +214,8 @@ spec actualPgVersion = do
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "0-0/*" ]
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=minimal"]
}
context "when patching with an empty body" $ do
@@ -272,7 +286,8 @@ spec actualPgVersion = do
`shouldRespondWith` "[]"
{
matchStatus = 200,
matchHeaders = ["Content-Range" <:> "*/*"]
matchHeaders = ["Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=representation"]
}
it "makes no updates and returns 200 with return=rep and with ?select=" $
@@ -280,7 +295,8 @@ spec actualPgVersion = do
`shouldRespondWith` "[]"
{
matchStatus = 200,
matchHeaders = ["Content-Range" <:> "*/*"]
matchHeaders = ["Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=representation"]
}
it "makes no updates and returns 200 with return=rep and with ?select= for overloaded computed columns" $
@@ -288,7 +304,8 @@ spec actualPgVersion = do
`shouldRespondWith` "[]"
{
matchStatus = 200,
matchHeaders = ["Content-Range" <:> "*/*"]
matchHeaders = ["Content-Range" <:> "*/*"
, "Preference-Applied" <:> "return=representation"]
}
context "with unicode values" $
@@ -343,7 +360,7 @@ spec actualPgVersion = do
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
]|]
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "updates with limit/offset using table default values(field-with_sep) when json keys are undefined" $ do
@@ -355,7 +372,7 @@ spec actualPgVersion = do
{"id":3,"name":"Tres"}
]|]
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "updates table default values(field-with_sep) when json keys are undefined" $ do
@@ -367,7 +384,7 @@ spec actualPgVersion = do
{"id":3,"name":"Tres","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}
]|]
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
it "updates view default values(field-with_sep) when json keys are undefined" $
@@ -381,7 +398,7 @@ spec actualPgVersion = do
{"id":3,"name":"Default","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":null,"field-with_sep":3}
]|]
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
, matchHeaders = ["Preference-Applied" <:> "missing=default, return=representation"]
}
-- https://github.com/PostgREST/postgrest/issues/2861
@@ -391,14 +408,18 @@ spec actualPgVersion = do
[("Prefer", "return=representation")]
[json|{"bit": "11100"}|]
`shouldRespondWith` [json|[{ "bit": "11100", "char": "aaaaa" }]|]
{ matchStatus = 200 }
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "return=representation"]
}
it "should update a char column with length" $
request methodPatch "/bitchar_with_length?select=bit,char&bit=eq.00000"
[("Prefer", "return=representation")]
[json|{"char": "zzzyy"}|]
`shouldRespondWith` [json|[{ "bit": "00000", "char": "zzzyy" }]|]
{ matchStatus = 200 }
{ matchStatus = 200
, matchHeaders = ["Preference-Applied" <:> "return=representation"]
}
context "tables with self reference foreign keys" $ do
context "embeds children after update" $ do
@@ -410,8 +431,8 @@ spec actualPgVersion = do
[json|
[ { "id": 0, "name": "tardis-patched", "web_content": [ { "name": "fezz" }, { "name": "foo" }, { "name": "bar" } ]} ]
|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
it "with filters" $
@@ -422,8 +443,8 @@ spec actualPgVersion = do
[json|
[ { "id": 0, "name": "tardis-patched", "web_content": [ { "name": "fezz" }, { "name": "foo" } ]} ]
|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
context "embeds parent, children and grandchildren after update" $ do
@@ -444,8 +465,8 @@ spec actualPgVersion = do
]
}
] |]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
it "with filters" $
@@ -464,8 +485,8 @@ spec actualPgVersion = do
]
}
] |]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
context "embeds children after update without explicitly including the id in the ?select" $ do
@@ -477,8 +498,8 @@ spec actualPgVersion = do
[json|
[ { "name": "tardis-patched", "web_content": [ { "name": "fezz" }, { "name": "foo" }, { "name": "bar" } ]} ]
|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
it "with filters" $
@@ -489,8 +510,8 @@ spec actualPgVersion = do
[json|
[ { "name": "tardis-patched", "web_content": [ { "name": "bar" } ]} ]
|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
context "tables with foreign keys referencing other tables" $ do
@@ -511,8 +532,8 @@ spec actualPgVersion = do
]
}
]|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
it "with filters" $
@@ -529,8 +550,8 @@ spec actualPgVersion = do
]
}
]|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
@@ -546,8 +567,8 @@ spec actualPgVersion = do
"students_info":{"address":"Street 1"}
}
]|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
request methodPatch "/students_info?id=eq.1&select=address,students(name)"
[("Prefer", "return=representation")]
@@ -559,8 +580,8 @@ spec actualPgVersion = do
"students":{"name": "John Doe"}
}
]|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
it "with filters" $ do
@@ -574,8 +595,8 @@ spec actualPgVersion = do
"students_info": null
}
]|]
{ matchStatus = 200,
matchHeaders = [matchContentTypeJson]
{ matchStatus = 200
, matchHeaders = [matchContentTypeJson, "Preference-Applied" <:> "return=representation"]
}
context "table with limited privileges" $ do
@@ -586,7 +607,8 @@ spec actualPgVersion = do
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [matchHeaderAbsent hContentType]
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal"]
}
it "can update without return=minimal and no explicit select" $
@@ -773,7 +795,7 @@ spec actualPgVersion = do
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "0-0/*" ]
, "Content-Range" <:> "0-0/*"]
}
it "parses values in payload and formats individually selected values in return=representation" $
@@ -782,8 +804,9 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"id":2, "label_color": "#221100"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
@@ -792,8 +815,9 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"id":2,"name":"Essay","label_color":"#221100","due_at":"2019-01-03T11:00:20Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"100000000000000.13"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats star mixed selected values in return=representation" $
@@ -803,8 +827,9 @@ spec actualPgVersion = do
-- end up with due_at twice here but that's unrelated to data reps
[json| [{"due_at":"2019-01-03T11:00:00Z","id":2,"name":"Essay","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":null,"created_at":0,"budget":"100000000000000.13"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
context "for multiple rows" $ do
it "parses values in payload and formats individually selected values in return=representation" $
@@ -817,8 +842,9 @@ spec actualPgVersion = do
{"id":3, "name": "Algebra", "label_color": "#221100"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-2/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
@@ -831,8 +857,9 @@ spec actualPgVersion = do
{"id":3,"name":"Algebra","label_color":"#221100","due_at":"2019-01-03T11:00:00Z","icon_image":"3q2+7w==","created_at":1513213350,"budget":"0.00"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-2/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
context "with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $
@@ -843,8 +870,9 @@ spec actualPgVersion = do
{"id":2,"name":"Essay","label_color":"#000100","due_at":"2019-01-03T11:00:00Z","icon_image":null,"created_at":1513213350,"budget":"100000000000000.13"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "fails if at least one specified column doesn't exist" $
@@ -878,8 +906,9 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"id":2, "label_color": "#221100"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
@@ -888,8 +917,9 @@ spec actualPgVersion = do
`shouldRespondWith`
[json| [{"id":2, "name": "Essay", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:20Z"}] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
context "for multiple rows" $ do
it "parses values in payload and formats individually selected values in return=representation" $
@@ -902,8 +932,9 @@ spec actualPgVersion = do
{"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-2/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
it "parses values in payload and formats values in return=representation" $
@@ -916,8 +947,9 @@ spec actualPgVersion = do
{"id":3, "name": "Algebra", "label_color": "#221100", "dark_color":"#110880", "due_at":"2019-01-03T11:00:00Z"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-2/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-2/*"
, "Preference-Applied" <:> "return=representation"]
}
context "with ?columns parameter" $ do
it "ignores json keys not included in ?columns; parses only the ones specified" $
@@ -928,8 +960,9 @@ spec actualPgVersion = do
{"id":2, "name": "Essay", "label_color": "#000100", "dark_color": "#000080", "due_at":"2019-01-03T11:00:00Z"}
] |]
{ matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8",
"Content-Range" <:> "0-0/*"]
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"
, "Content-Range" <:> "0-0/*"
, "Preference-Applied" <:> "return=representation"]
}
it "fails if at least one specified column doesn't exist" $
+40 -18
View File
@@ -29,7 +29,7 @@ spec actualPgVersion =
{ "name": "C", "rank": 1 }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
}
it "INSERTs and UPDATEs row on composite pk conflict" $
@@ -42,7 +42,7 @@ spec actualPgVersion =
{ "first_name": "Peter S.", "last_name": "Yang", "salary": "$42,000.00", "company": null, "occupation": null }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
}
when (actualPgVersion >= pgVersion110) $
@@ -56,13 +56,14 @@ spec actualPgVersion =
{ "name": "Roma", "year": 2021, "car_brand_name": "Ferrari" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
}
it "succeeds when the payload has no elements" $
request methodPost "/articles" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
[json|[]|] `shouldRespondWith`
[json|[]|] { matchStatus = 201 , matchHeaders = [matchContentTypeJson] }
[json|[]|] { matchStatus = 201
, matchHeaders = [matchContentTypeJson] }
it "INSERTs and UPDATEs rows on single unique key conflict" $
request methodPost "/single_unique?on_conflict=unique_key" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
@@ -74,7 +75,7 @@ spec actualPgVersion =
{ "unique_key": 2, "value": "C" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
}
it "INSERTs and UPDATEs rows on compound unique keys conflict" $
@@ -87,7 +88,7 @@ spec actualPgVersion =
{ "key1": 1, "key2": 2, "value": "C" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation", matchContentTypeJson]
}
context "when Prefer: resolution=ignore-duplicates is specified" $ do
@@ -100,7 +101,7 @@ spec actualPgVersion =
{ "name": "PHP", "rank": 9 }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson]
}
it "INSERTs and ignores rows on composite pk conflict" $
@@ -112,7 +113,7 @@ spec actualPgVersion =
{ "first_name": "Sara M.", "last_name": "Torpey", "salary": "$60,000.00", "company": "Burstein-Applebee", "occupation": "Soil scientist" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson]
}
when (actualPgVersion >= pgVersion110) $
@@ -125,7 +126,7 @@ spec actualPgVersion =
{ "name": "Huracán", "year": 2021, "car_brand_name": "Lamborghini" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation", matchContentTypeJson]
}
it "INSERTs and ignores rows on single unique key conflict" $
@@ -142,7 +143,7 @@ spec actualPgVersion =
{ "unique_key": 3, "value": "D" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation"]
}
it "INSERTs and UPDATEs rows on compound unique keys conflict" $
@@ -159,7 +160,7 @@ spec actualPgVersion =
{ "key1": 1, "key2": 3, "value": "D" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation"]
}
it "succeeds if the table has only PK cols and no other cols" $ do
@@ -168,7 +169,7 @@ spec actualPgVersion =
`shouldRespondWith`
[json|[ { "id": 3} ]|]
{ matchStatus = 201 ,
matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates",
matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation",
matchContentTypeJson] }
request methodPost "/only_pk" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
@@ -176,22 +177,30 @@ spec actualPgVersion =
`shouldRespondWith`
[json|[ { "id": 1 }, { "id": 2 }, { "id": 4} ]|]
{ matchStatus = 201 ,
matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates",
matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation",
matchContentTypeJson] }
it "succeeds and ignores the Prefer: resolution header(no Preference-Applied present) if the table has no PK" $
request methodPost "/no_pk" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")]
[json|[ { "a": "1", "b": "0" } ]|]
`shouldRespondWith`
[json|[ { "a": "1", "b": "0" } ]|] { matchStatus = 201 , matchHeaders = [matchContentTypeJson] }
[json|[ { "a": "1", "b": "0" } ]|]
{ matchStatus = 201
, matchHeaders = [matchContentTypeJson
, "Preference-Applied" <:> "return=representation"] }
it "succeeds if not a single resource is created" $ do
request methodPost "/tiobe_pls" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
[json|[ { "name": "Java", "rank": 1 } ]|] `shouldRespondWith`
[json|[]|] { matchStatus = 201 , matchHeaders = [matchContentTypeJson] }
[json|[]|]
{ matchStatus = 201
, matchHeaders = [matchContentTypeJson] }
request methodPost "/tiobe_pls" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")]
[json|[ { "name": "Java", "rank": 1 }, { "name": "C", "rank": 2 } ]|] `shouldRespondWith`
[json|[]|] { matchStatus = 201 , matchHeaders = [matchContentTypeJson] }
[json|[]|]
{ matchStatus = 201
, matchHeaders = [matchContentTypeJson] }
context "with PUT" $ do
context "Restrictions" $ do
@@ -408,7 +417,7 @@ spec actualPgVersion =
{ "idUnitTest": 2, "nameUnitTest": "name of unittest 2" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates"]
, matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates, return=representation"]
}
it "works with POST and ignore-duplicates headers" $ do
@@ -423,7 +432,7 @@ spec actualPgVersion =
{ "idUnitTest": 2, "nameUnitTest": "name of unittest 2" }
]|]
{ matchStatus = 201
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"]
, matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates, return=representation"]
}
it "works with PUT" $ do
@@ -436,3 +445,16 @@ spec actualPgVersion =
}
get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith`
[json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|]
it "works with request method PUT and return=minimal" $ do
request methodPut "/UnitTest?idUnitTest=eq.1"
[("Prefer", "return=minimal")]
[json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|]
`shouldRespondWith`
""
{ matchStatus = 204
, matchHeaders = [matchHeaderAbsent hContentType
, "Preference-Applied" <:> "return=minimal"]
}
get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith`
[json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|]