feat: add Preference-Applied header in response for Prefer: return=representation/minimal/headers-only
This commit is contained in:
committed by
Steve Chavez
parent
aa53623aac
commit
d490bf09fd
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -20,4 +20,5 @@ main =
|
||||
, "src/PostgREST/MediaType.hs"
|
||||
, "src/PostgREST/Config.hs"
|
||||
, "src/PostgREST/Plan.hs"
|
||||
, "src/PostgREST/Response.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" $
|
||||
|
||||
@@ -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" $
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" $
|
||||
|
||||
@@ -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" } ]|]
|
||||
|
||||
Reference in New Issue
Block a user