diff --git a/postgrest.cabal b/postgrest.cabal index 837d07d44..9c04aed30 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -273,6 +273,7 @@ test-suite querycost , heredoc >= 0.2 && < 0.3 , hspec >= 2.3 && < 2.9 , hspec-wai >= 0.10 && < 0.12 + , hspec-wai-json >= 0.10 && < 0.12 , http-types >= 0.12.3 && < 0.13 , lens >= 4.14 && < 5.2 , lens-aeson >= 1.0.1 && < 1.2 diff --git a/test/spec/Feature/OptionsSpec.hs b/test/spec/Feature/OptionsSpec.hs index ed5188a4a..78e5e5d79 100644 --- a/test/spec/Feature/OptionsSpec.hs +++ b/test/spec/Feature/OptionsSpec.hs @@ -91,7 +91,7 @@ spec actualPgVersion = describe "Allow header" $ do context "a function" $ do it "includes the POST method for a volatile function" $ do - r <- request methodOptions "/rpc/reset_items_tables" [] "" + r <- request methodOptions "/rpc/reset_table" [] "" liftIO $ simpleHeaders r `shouldSatisfy` matchHeader "Allow" "OPTIONS,POST" diff --git a/test/spec/Feature/Query/DeleteSpec.hs b/test/spec/Feature/Query/DeleteSpec.hs index 24741cfd2..0feee4eb5 100644 --- a/test/spec/Feature/Query/DeleteSpec.hs +++ b/test/spec/Feature/Query/DeleteSpec.hs @@ -1,5 +1,7 @@ module Feature.Query.DeleteSpec where +import Data.Aeson.QQ + import Network.Wai (Application) import Network.HTTP.Types @@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON import Protolude hiding (get) import SpecHelper +tblDataBefore = [aesonQQ|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "item-2" } + , { "id": 3, "name": "item-3" } + ]|] + spec :: SpecWith ((), Application) spec = describe "Deleting" $ do @@ -117,69 +125,25 @@ spec = } context "limited delete" $ do - it "works with the limit and offset query params" $ do - get "/limited_delete_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] + it "works with the limit and offset query params" $ + baseTable "limited_delete_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/limited_delete_items?order=id&limit=1&offset=1" mempty + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 3, "name": "item-3" } + ]|] - request methodDelete "/limited_delete_items?order=id&limit=1&offset=1" - [("Prefer", "tx=commit")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_delete_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_delete_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works with the limit query param plus a filter" $ do - get "/limited_delete_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodDelete "/limited_delete_items?order=id&limit=1&id=gt.1" - [("Prefer", "tx=commit")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_delete_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_delete_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works with the limit query param plus a filter" $ + baseTable "limited_delete_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/limited_delete_items?order=id&limit=1&id=gt.1" mempty + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 3, "name": "item-3" } + ]|] it "fails without an explicit order by" $ request methodDelete "/limited_delete_items?limit=1&offset=1" @@ -207,98 +171,32 @@ spec = }|] { matchStatus = 400 } - it "works with views with an explicit order by unique col" $ do - get "/limited_delete_items_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] + it "works with views with an explicit order by unique col" $ + baseTable "limited_delete_items_view" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/limited_delete_items_view?order=id&limit=1&offset=1" mempty + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 3, "name": "item-3" } + ]|] - request methodDelete "/limited_delete_items_view?order=id&limit=1&offset=1" - [("Prefer", "tx=commit")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } + it "works with views with an explicit order by composite pk" $ + baseTable "limited_delete_items_cpk_view" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/limited_delete_items_cpk_view?order=id,name&limit=1&offset=1" mempty + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 3, "name": "item-3" } + ]|] - get "/limited_delete_items_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_delete_items_view"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works with views with an explicit order by composite pk" $ do - get "/limited_delete_items_cpk_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodDelete "/limited_delete_items_cpk_view?order=id,name&limit=1&offset=1" - [("Prefer", "tx=commit")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_delete_items_cpk_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_delete_items_cpk_view"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works on a table without a pk by ordering by 'ctid'" $ do - get "/limited_delete_items_no_pk" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodDelete "/limited_delete_items_no_pk?order=ctid&limit=1&offset=1" - [("Prefer", "tx=commit")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_delete_items_no_pk" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_delete_items_no_pk"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works on a table without a pk by ordering by 'ctid'" $ + baseTable "limited_delete_items_no_pk" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/limited_delete_items_no_pk?order=ctid&limit=1&offset=1" mempty + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 3, "name": "item-3" } + ]|] diff --git a/test/spec/Feature/Query/PgSafeUpdateSpec.hs b/test/spec/Feature/Query/PgSafeUpdateSpec.hs index 66e73371b..452133a6f 100644 --- a/test/spec/Feature/Query/PgSafeUpdateSpec.hs +++ b/test/spec/Feature/Query/PgSafeUpdateSpec.hs @@ -1,5 +1,7 @@ module Feature.Query.PgSafeUpdateSpec where +import Data.Aeson.QQ + import Network.Wai (Application) import Network.HTTP.Types @@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON import Protolude hiding (get, put) import SpecHelper +tblDataBefore = [aesonQQ|[ + { "id": 1, "name": "item-1", "observation": null } + , { "id": 2, "name": "item-2", "observation": null } + , { "id": 3, "name": "item-3", "observation": null } + ]|] + spec :: SpecWith ((), Application) spec = describe "Enabling pg-safeupdate" $ do @@ -27,40 +35,16 @@ spec = }|] { matchStatus = 400 } - it "allows full table update if a filter is present" $ do - get "/safe_update_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1", "observation": null } - , { "id": 2, "name": "item-2", "observation": null } - , { "id": 3, "name": "item-3", "observation": null } - ]|] - - request methodPatch "/safe_update_items?id=gt.0" - [("Prefer", "tx=commit"), ("Prefer", "count=exact")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Content-Range" <:> "0-2/3" - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/safe_update_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "updated-item", "observation": null } - , { "id": 2, "name": "updated-item", "observation": null } - , { "id": 3, "name": "updated-item", "observation": null } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "safe_update_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - + it "allows full table update if a filter is present" $ + baseTable "safe_update_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/safe_update_items?id=gt.0" [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "updated-item", "observation": null } + , { "id": 2, "name": "updated-item", "observation": null } + , { "id": 3, "name": "updated-item", "observation": null } + ]|] context "Full table delete" $ do it "does not delete and throws error if no condition is present" $ @@ -74,101 +58,32 @@ spec = }|] { matchStatus = 400 } - it "allows full table delete if a filter is present" $ do - get "/safe_delete_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1", "observation": null } - , { "id": 2, "name": "item-2", "observation": null } - , { "id": 3, "name": "item-3", "observation": null } - ]|] - - request methodDelete "/safe_delete_items?id=gt.0" - [("Prefer", "tx=commit"), ("Prefer", "count=exact")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Content-Range" <:> "*/3" - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/safe_delete_items?order=id" - `shouldRespondWith` - [json|[]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "safe_delete_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "allows full table delete if a filter is present" $ + baseTable "safe_delete_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/safe_delete_items?id=gt.0" mempty + `shouldMutateInto` + [json|[]|] disabledSpec :: SpecWith ((), Application) disabledSpec = describe "Disabling pg-safeupdate" $ do context "Full table update" $ do - it "works if no condition is present" $ do - get "/unsafe_update_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1", "observation": null } - , { "id": 2, "name": "item-2", "observation": null } - , { "id": 3, "name": "item-3", "observation": null } - ]|] - - request methodPatch "/unsafe_update_items" - [("Prefer", "tx=commit"), ("Prefer", "count=exact")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Content-Range" <:> "0-2/3" - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/unsafe_update_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "updated-item", "observation": null } - , { "id": 2, "name": "updated-item", "observation": null } - , { "id": 3, "name": "updated-item", "observation": null } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "unsafe_update_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works if no condition is present" $ + baseTable "unsafe_update_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/unsafe_update_items" [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "updated-item", "observation": null } + , { "id": 2, "name": "updated-item", "observation": null } + , { "id": 3, "name": "updated-item", "observation": null } + ]|] context "Full table delete" $ do - it "works if no condition is present" $ do - get "/unsafe_delete_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1", "observation": null } - , { "id": 2, "name": "item-2", "observation": null } - , { "id": 3, "name": "item-3", "observation": null } - ]|] - - request methodDelete "/unsafe_delete_items" - [("Prefer", "tx=commit"), ("Prefer", "count=exact")] - mempty - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Content-Range" <:> "*/3" - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/unsafe_delete_items?order=id" - `shouldRespondWith` - [json|[]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "unsafe_delete_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works if no condition is present" $ + baseTable "unsafe_delete_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodDelete "/unsafe_delete_items" mempty + `shouldMutateInto` + [json|[]|] diff --git a/test/spec/Feature/Query/UpdateSpec.hs b/test/spec/Feature/Query/UpdateSpec.hs index e7754e35b..0d774e271 100644 --- a/test/spec/Feature/Query/UpdateSpec.hs +++ b/test/spec/Feature/Query/UpdateSpec.hs @@ -1,5 +1,7 @@ module Feature.Query.UpdateSpec where +import Data.Aeson.QQ + import Network.Wai (Application) import Test.Hspec hiding (pendingWith) @@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON import Protolude hiding (get) import SpecHelper +tblDataBefore = [aesonQQ|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "item-2" } + , { "id": 3, "name": "item-3" } + ]|] + spec :: SpecWith ((), Application) spec = do describe "Patching record" $ do @@ -388,105 +396,41 @@ spec = do } context "limited update" $ do - it "works with the limit query param" $ do - get "/limited_update_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] + it "works with the limit query param" $ + baseTable "limited_update_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items?order=id&limit=2" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "updated-item" } + , { "id": 2, "name": "updated-item" } + , { "id": 3, "name": "item-3" } + ]|] - request methodPatch "/limited_update_items?order=id&limit=2" - [("Prefer", "tx=commit"), ("Prefer", "count=exact")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Content-Range" <:> "0-1/2" - , "Preference-Applied" <:> "tx=commit" ] - } + it "works with the limit query param plus a filter" $ + baseTable "limited_update_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items?order=id&limit=1&id=gt.2" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "item-2" } + , { "id": 3, "name": "updated-item" } + ]|] - get "/limited_update_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "updated-item" } - , { "id": 2, "name": "updated-item" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works with the limit query param plus a filter" $ do - get "/limited_update_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPatch "/limited_update_items?order=id&limit=1&id=gt.2" - [("Prefer", "tx=commit")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_update_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "updated-item" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works with the limit and offset query params" $ do - get "/limited_update_items" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPatch "/limited_update_items?order=id&limit=1&offset=1" - [("Prefer", "tx=commit")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_update_items?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "updated-item" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works with the limit and offset query params" $ + baseTable "limited_update_items" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items?order=id&limit=1&offset=1" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "updated-item" } + , { "id": 3, "name": "item-3" } + ]|] it "fails without an explicit order by" $ request methodPatch "/limited_update_items?limit=1&offset=1" @@ -514,102 +458,38 @@ spec = do }|] { matchStatus = 400 } - it "works with views with an explicit order by unique col" $ do - get "/limited_update_items_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] + it "works with views with an explicit order by unique col" $ + baseTable "limited_update_items_view" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items_view?order=id&limit=1&offset=1" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "updated-item" } + , { "id": 3, "name": "item-3" } + ]|] - request methodPatch "/limited_update_items_view?order=id&limit=1&offset=1" - [("Prefer", "tx=commit")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } + it "works with views with an explicit order by composite pk" $ + baseTable "limited_update_items_cpk_view" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items_cpk_view?order=id,name&limit=1&offset=1" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "item-1" } + , { "id": 2, "name": "updated-item" } + , { "id": 3, "name": "item-3" } + ]|] - get "/limited_update_items_view?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "updated-item" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items_view"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - it "works with views with an explicit order by composite pk" $ do - get "/limited_update_items_cpk_view" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPatch "/limited_update_items_cpk_view?order=id,name&limit=1&offset=1" - [("Prefer", "tx=commit")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_update_items_cpk_view?order=id,name" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "updated-item" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items_cpk_view"} |] - `shouldRespondWith` "" - { matchStatus = 204 } - - - it "works on a table without a pk by ordering by 'ctid'" $ do - get "/limited_update_items_no_pk" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "item-1" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPatch "/limited_update_items_no_pk?order=ctid&limit=1" - [("Prefer", "tx=commit")] - [json| {"name": "updated-item"} |] - `shouldRespondWith` - "" - { matchStatus = 204 - , matchHeaders = [ matchHeaderAbsent hContentType - , "Preference-Applied" <:> "tx=commit" ] - } - - get "/limited_update_items_no_pk?order=id" - `shouldRespondWith` - [json|[ - { "id": 1, "name": "updated-item" } - , { "id": 2, "name": "item-2" } - , { "id": 3, "name": "item-3" } - ]|] - - request methodPost "/rpc/reset_items_tables" - [("Prefer", "tx=commit")] - [json| {"tbl_name": "limited_update_items_no_pk"} |] - `shouldRespondWith` "" - { matchStatus = 204 } + it "works on a table without a pk by ordering by 'ctid'" $ + baseTable "limited_update_items_no_pk" "id" tblDataBefore + `mutatesWith` + requestMutation methodPatch "/limited_update_items_no_pk?order=ctid&limit=1" + [json| {"name": "updated-item"} |] + `shouldMutateInto` + [json|[ + { "id": 1, "name": "updated-item" } + , { "id": 2, "name": "item-2" } + , { "id": 3, "name": "item-3" } + ]|] diff --git a/test/spec/SpecHelper.hs b/test/spec/SpecHelper.hs index 40e39c0cf..dc498bd26 100644 --- a/test/spec/SpecHelper.hs +++ b/test/spec/SpecHelper.hs @@ -18,6 +18,7 @@ import Text.Regex.TDFA ((=~)) import Network.HTTP.Types import Test.Hspec import Test.Hspec.Wai +import Test.Hspec.Wai.JSON import Text.Heredoc import PostgREST.Config (AppConfig (..), @@ -27,7 +28,7 @@ import PostgREST.Config (AppConfig (..), parseSecret) import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..)) import PostgREST.MediaType (MediaType (..)) -import Protolude hiding (toS) +import Protolude hiding (get, toS) import Protolude.Conv (toS) matchContentTypeJson :: MatchHeader @@ -243,3 +244,35 @@ isErrorFormat s = obj = decode s :: Maybe (M.Map Text Value) keys = maybe S.empty M.keysSet obj validKeys = S.fromList ["message", "details", "hint", "code"] + +-- | Follows these steps to verify if the table data changed in the db: +-- * Verifies the table data in the db before the change +-- * Does the mutation +-- * Verifies that the table data changed in the db +-- * Resets the table with the original data +shouldMutateInto :: MutationCheck -> ResponseMatcher -> WaiExpectation () +shouldMutateInto (MutationCheck (BaseTable tblName tblOrd dataBefore) mutation) dataAfter = do + get ("/" <> tblName) `shouldRespondWith` [json|#{dataBefore}|] + mutation + get ("/" <> tblName <> "?order=" <> tblOrd) `shouldRespondWith` dataAfter + request methodPost "/rpc/reset_table" + [("Prefer", "tx=commit")] + [json| {"tbl_name": #{decodeUtf8 tblName}, "tbl_data": #{dataBefore}} |] + `shouldRespondWith` 204 + +-- | How the base table data will change using the requested mutation +mutatesWith :: BaseTable -> WaiExpectation () -> MutationCheck +mutatesWith = MutationCheck + +-- | The original table data before it is modified. +-- The column order is needed for an accurate comparison after the mutation +baseTable :: ByteString -> ByteString -> Value -> BaseTable +baseTable = BaseTable + +-- | The mutation (update/delete) that will be applied to the base table +requestMutation :: Method -> ByteString -> BL.ByteString -> WaiExpectation () +requestMutation method path body = + request method path [("Prefer", "tx=commit")] body `shouldRespondWith` 204 + +data BaseTable = BaseTable ByteString ByteString Value +data MutationCheck = MutationCheck BaseTable (WaiExpectation ()) diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql index d8deb1c8f..a8a8bb36b 100644 --- a/test/spec/fixtures/schema.sql +++ b/test/spec/fixtures/schema.sql @@ -2531,13 +2531,15 @@ select *, 'static'::text as static from limited_delete_items; create view limited_delete_items_cpk_view as select * from limited_delete_items_cpk; -create function reset_items_tables(tbl_name text default '') returns void as $_$ begin +create function reset_table(tbl_name text default '', tbl_data json default '[]') returns void as $_$ begin execute format( $$ delete from %I where true; -- WHERE is required for pg-safeupdate tests - insert into %I values (1, 'item-1'), (2, 'item-2'), (3, 'item-3'); + insert into %I + select * from json_populate_recordset(null::%I, $1); $$::text, - tbl_name, tbl_name); + tbl_name, tbl_name, tbl_name) + using tbl_data; end; $_$ language plpgsql volatile; -- tables for ensuring we generate real junctions for many-to-many relationships