From d490bf09fdf91540319f0dac128e0c40224b2a54 Mon Sep 17 00:00:00 2001 From: Taimoor Zaeem Date: Fri, 4 Aug 2023 09:08:58 +0500 Subject: [PATCH] feat: add Preference-Applied header in response for Prefer: return=representation/minimal/headers-only --- CHANGELOG.md | 1 + src/PostgREST/ApiRequest/Preferences.hs | 22 ++-- src/PostgREST/Plan.hs | 4 +- src/PostgREST/Query/Statements.hs | 4 +- src/PostgREST/Response.hs | 77 ++++++++--- test/doc/Main.hs | 1 + test/spec/Feature/Query/DeleteSpec.hs | 9 +- test/spec/Feature/Query/InsertSpec.hs | 56 +++++--- test/spec/Feature/Query/SingularSpec.hs | 6 +- test/spec/Feature/Query/UpdateSpec.hs | 165 ++++++++++++++---------- test/spec/Feature/Query/UpsertSpec.hs | 58 ++++++--- 11 files changed, 261 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bad4f5199..d763b67af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/PostgREST/ApiRequest/Preferences.hs b/src/PostgREST/ApiRequest/Preferences.hs index 302d7a00e..b0846b2fb 100644 --- a/src/PostgREST/ApiRequest/Preferences.hs +++ b/src/PostgREST/ApiRequest/Preferences.hs @@ -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" diff --git a/src/PostgREST/Plan.hs b/src/PostgREST/Plan.hs index a3586771a..4c805ef92 100644 --- a/src/PostgREST/Plan.hs +++ b/src/PostgREST/Plan.hs @@ -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 diff --git a/src/PostgREST/Query/Statements.hs b/src/PostgREST/Query/Statements.hs index 4fc2b7685..634568fb9 100644 --- a/src/PostgREST/Query/Statements.hs +++ b/src/PostgREST/Query/Statements.hs @@ -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 <> ") " <> diff --git a/src/PostgREST/Response.hs b/src/PostgREST/Response.hs index 39e084ec7..160cb1834 100644 --- a/src/PostgREST/Response.hs +++ b/src/PostgREST/Response.hs @@ -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 diff --git a/test/doc/Main.hs b/test/doc/Main.hs index 1a0b5ac42..e43952ab1 100644 --- a/test/doc/Main.hs +++ b/test/doc/Main.hs @@ -20,4 +20,5 @@ main = , "src/PostgREST/MediaType.hs" , "src/PostgREST/Config.hs" , "src/PostgREST/Plan.hs" + , "src/PostgREST/Response.hs" ] diff --git a/test/spec/Feature/Query/DeleteSpec.hs b/test/spec/Feature/Query/DeleteSpec.hs index 698c7ea46..c36ed2087 100644 --- a/test/spec/Feature/Query/DeleteSpec.hs +++ b/test/spec/Feature/Query/DeleteSpec.hs @@ -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" $ diff --git a/test/spec/Feature/Query/InsertSpec.hs b/test/spec/Feature/Query/InsertSpec.hs index ea799efbe..c5a8eb9d3 100644 --- a/test/spec/Feature/Query/InsertSpec.hs +++ b/test/spec/Feature/Query/InsertSpec.hs @@ -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" $ diff --git a/test/spec/Feature/Query/SingularSpec.hs b/test/spec/Feature/Query/SingularSpec.hs index b8ff4fa8f..aa83d10dd 100644 --- a/test/spec/Feature/Query/SingularSpec.hs +++ b/test/spec/Feature/Query/SingularSpec.hs @@ -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 diff --git a/test/spec/Feature/Query/UpdateSpec.hs b/test/spec/Feature/Query/UpdateSpec.hs index 424e8bcd9..362a00e32 100644 --- a/test/spec/Feature/Query/UpdateSpec.hs +++ b/test/spec/Feature/Query/UpdateSpec.hs @@ -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" $ diff --git a/test/spec/Feature/Query/UpsertSpec.hs b/test/spec/Feature/Query/UpsertSpec.hs index 4aa396198..b317db143 100644 --- a/test/spec/Feature/Query/UpsertSpec.hs +++ b/test/spec/Feature/Query/UpsertSpec.hs @@ -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" } ]|]