refactor: simplify tests where mutations need to be verified in the db

This commit is contained in:
Laurence Isla
2022-08-15 12:06:54 -05:00
committed by GitHub
parent 7de8d5446a
commit ba3ba9fd5b
7 changed files with 208 additions and 479 deletions
+1
View File
@@ -273,6 +273,7 @@ test-suite querycost
, heredoc >= 0.2 && < 0.3 , heredoc >= 0.2 && < 0.3
, hspec >= 2.3 && < 2.9 , hspec >= 2.3 && < 2.9
, hspec-wai >= 0.10 && < 0.12 , hspec-wai >= 0.10 && < 0.12
, hspec-wai-json >= 0.10 && < 0.12
, http-types >= 0.12.3 && < 0.13 , http-types >= 0.12.3 && < 0.13
, lens >= 4.14 && < 5.2 , lens >= 4.14 && < 5.2
, lens-aeson >= 1.0.1 && < 1.2 , lens-aeson >= 1.0.1 && < 1.2
+1 -1
View File
@@ -91,7 +91,7 @@ spec actualPgVersion = describe "Allow header" $ do
context "a function" $ do context "a function" $ do
it "includes the POST method for a volatile 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 $ liftIO $
simpleHeaders r `shouldSatisfy` simpleHeaders r `shouldSatisfy`
matchHeader "Allow" "OPTIONS,POST" matchHeader "Allow" "OPTIONS,POST"
+33 -135
View File
@@ -1,5 +1,7 @@
module Feature.Query.DeleteSpec where module Feature.Query.DeleteSpec where
import Data.Aeson.QQ
import Network.Wai (Application) import Network.Wai (Application)
import Network.HTTP.Types import Network.HTTP.Types
@@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
tblDataBefore = [aesonQQ|[
{ "id": 1, "name": "item-1" }
, { "id": 2, "name": "item-2" }
, { "id": 3, "name": "item-3" }
]|]
spec :: SpecWith ((), Application) spec :: SpecWith ((), Application)
spec = spec =
describe "Deleting" $ do describe "Deleting" $ do
@@ -117,70 +125,26 @@ spec =
} }
context "limited delete" $ do context "limited delete" $ do
it "works with the limit and offset query params" $ do it "works with the limit and offset query params" $
get "/limited_delete_items" baseTable "limited_delete_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodDelete "/limited_delete_items?order=id&limit=1&offset=1" mempty
{ "id": 1, "name": "item-1" } `shouldMutateInto`
, { "id": 2, "name": "item-2" }
, { "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|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works with the limit query param plus a filter" $
[("Prefer", "tx=commit")] baseTable "limited_delete_items" "id" tblDataBefore
[json| {"tbl_name": "limited_delete_items"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodDelete "/limited_delete_items?order=id&limit=1&id=gt.1" mempty
{ matchStatus = 204 } `shouldMutateInto`
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|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables"
[("Prefer", "tx=commit")]
[json| {"tbl_name": "limited_delete_items"} |]
`shouldRespondWith` ""
{ matchStatus = 204 }
it "fails without an explicit order by" $ it "fails without an explicit order by" $
request methodDelete "/limited_delete_items?limit=1&offset=1" request methodDelete "/limited_delete_items?limit=1&offset=1"
[("Prefer", "tx=commit")] [("Prefer", "tx=commit")]
@@ -207,98 +171,32 @@ spec =
}|] }|]
{ matchStatus = 400 } { matchStatus = 400 }
it "works with views with an explicit order by unique col" $ do it "works with views with an explicit order by unique col" $
get "/limited_delete_items_view" baseTable "limited_delete_items_view" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodDelete "/limited_delete_items_view?order=id&limit=1&offset=1" mempty
{ "id": 1, "name": "item-1" } `shouldMutateInto`
, { "id": 2, "name": "item-2" }
, { "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" ]
}
get "/limited_delete_items_view"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works with views with an explicit order by composite pk" $
[("Prefer", "tx=commit")] baseTable "limited_delete_items_cpk_view" "id" tblDataBefore
[json| {"tbl_name": "limited_delete_items_view"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodDelete "/limited_delete_items_cpk_view?order=id,name&limit=1&offset=1" mempty
{ matchStatus = 204 } `shouldMutateInto`
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|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works on a table without a pk by ordering by 'ctid'" $
[("Prefer", "tx=commit")] baseTable "limited_delete_items_no_pk" "id" tblDataBefore
[json| {"tbl_name": "limited_delete_items_cpk_view"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodDelete "/limited_delete_items_no_pk?order=ctid&limit=1&offset=1" mempty
{ matchStatus = 204 } `shouldMutateInto`
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|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 3, "name": "item-3" } , { "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 }
+28 -113
View File
@@ -1,5 +1,7 @@
module Feature.Query.PgSafeUpdateSpec where module Feature.Query.PgSafeUpdateSpec where
import Data.Aeson.QQ
import Network.Wai (Application) import Network.Wai (Application)
import Network.HTTP.Types import Network.HTTP.Types
@@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get, put) import Protolude hiding (get, put)
import SpecHelper 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 :: SpecWith ((), Application)
spec = spec =
describe "Enabling pg-safeupdate" $ do describe "Enabling pg-safeupdate" $ do
@@ -27,41 +35,17 @@ spec =
}|] }|]
{ matchStatus = 400 } { matchStatus = 400 }
it "allows full table update if a filter is present" $ do it "allows full table update if a filter is present" $
get "/safe_update_items" baseTable "safe_update_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodPatch "/safe_update_items?id=gt.0" [json| {"name": "updated-item"} |]
{ "id": 1, "name": "item-1", "observation": null } `shouldMutateInto`
, { "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|[ [json|[
{ "id": 1, "name": "updated-item", "observation": null } { "id": 1, "name": "updated-item", "observation": null }
, { "id": 2, "name": "updated-item", "observation": null } , { "id": 2, "name": "updated-item", "observation": null }
, { "id": 3, "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 }
context "Full table delete" $ do context "Full table delete" $ do
it "does not delete and throws error if no condition is present" $ it "does not delete and throws error if no condition is present" $
request methodDelete "/safe_delete_items" [] mempty request methodDelete "/safe_delete_items" [] mempty
@@ -74,101 +58,32 @@ spec =
}|] }|]
{ matchStatus = 400 } { matchStatus = 400 }
it "allows full table delete if a filter is present" $ do it "allows full table delete if a filter is present" $
get "/safe_delete_items" baseTable "safe_delete_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodDelete "/safe_delete_items?id=gt.0" mempty
{ "id": 1, "name": "item-1", "observation": null } `shouldMutateInto`
, { "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|[]|] [json|[]|]
request methodPost "/rpc/reset_items_tables"
[("Prefer", "tx=commit")]
[json| {"tbl_name": "safe_delete_items"} |]
`shouldRespondWith` ""
{ matchStatus = 204 }
disabledSpec :: SpecWith ((), Application) disabledSpec :: SpecWith ((), Application)
disabledSpec = disabledSpec =
describe "Disabling pg-safeupdate" $ do describe "Disabling pg-safeupdate" $ do
context "Full table update" $ do context "Full table update" $ do
it "works if no condition is present" $ do it "works if no condition is present" $
get "/unsafe_update_items" baseTable "unsafe_update_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodPatch "/unsafe_update_items" [json| {"name": "updated-item"} |]
{ "id": 1, "name": "item-1", "observation": null } `shouldMutateInto`
, { "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|[ [json|[
{ "id": 1, "name": "updated-item", "observation": null } { "id": 1, "name": "updated-item", "observation": null }
, { "id": 2, "name": "updated-item", "observation": null } , { "id": 2, "name": "updated-item", "observation": null }
, { "id": 3, "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 }
context "Full table delete" $ do context "Full table delete" $ do
it "works if no condition is present" $ do it "works if no condition is present" $
get "/unsafe_delete_items" baseTable "unsafe_delete_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodDelete "/unsafe_delete_items" mempty
{ "id": 1, "name": "item-1", "observation": null } `shouldMutateInto`
, { "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|[]|] [json|[]|]
request methodPost "/rpc/reset_items_tables"
[("Prefer", "tx=commit")]
[json| {"tbl_name": "unsafe_delete_items"} |]
`shouldRespondWith` ""
{ matchStatus = 204 }
+38 -158
View File
@@ -1,5 +1,7 @@
module Feature.Query.UpdateSpec where module Feature.Query.UpdateSpec where
import Data.Aeson.QQ
import Network.Wai (Application) import Network.Wai (Application)
import Test.Hspec hiding (pendingWith) import Test.Hspec hiding (pendingWith)
@@ -10,6 +12,12 @@ import Test.Hspec.Wai.JSON
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
tblDataBefore = [aesonQQ|[
{ "id": 1, "name": "item-1" }
, { "id": 2, "name": "item-2" }
, { "id": 3, "name": "item-3" }
]|]
spec :: SpecWith ((), Application) spec :: SpecWith ((), Application)
spec = do spec = do
describe "Patching record" $ do describe "Patching record" $ do
@@ -388,106 +396,42 @@ spec = do
} }
context "limited update" $ do context "limited update" $ do
it "works with the limit query param" $ do it "works with the limit query param" $
get "/limited_update_items" baseTable "limited_update_items" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodPatch "/limited_update_items?order=id&limit=2"
{ "id": 1, "name": "item-1" }
, { "id": 2, "name": "item-2" }
, { "id": 3, "name": "item-3" }
]|]
request methodPatch "/limited_update_items?order=id&limit=2"
[("Prefer", "tx=commit"), ("Prefer", "count=exact")]
[json| {"name": "updated-item"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Content-Range" <:> "0-1/2"
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items?order=id"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "updated-item" } { "id": 1, "name": "updated-item" }
, { "id": 2, "name": "updated-item" } , { "id": 2, "name": "updated-item" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works with the limit query param plus a filter" $
[("Prefer", "tx=commit")] baseTable "limited_update_items" "id" tblDataBefore
[json| {"tbl_name": "limited_update_items"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodPatch "/limited_update_items?order=id&limit=1&id=gt.2"
{ 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"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items?order=id"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 2, "name": "item-2" } , { "id": 2, "name": "item-2" }
, { "id": 3, "name": "updated-item" } , { "id": 3, "name": "updated-item" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works with the limit and offset query params" $
[("Prefer", "tx=commit")] baseTable "limited_update_items" "id" tblDataBefore
[json| {"tbl_name": "limited_update_items"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodPatch "/limited_update_items?order=id&limit=1&offset=1"
{ 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"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items?order=id"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 2, "name": "updated-item" } , { "id": 2, "name": "updated-item" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables"
[("Prefer", "tx=commit")]
[json| {"tbl_name": "limited_update_items"} |]
`shouldRespondWith` ""
{ matchStatus = 204 }
it "fails without an explicit order by" $ it "fails without an explicit order by" $
request methodPatch "/limited_update_items?limit=1&offset=1" request methodPatch "/limited_update_items?limit=1&offset=1"
[("Prefer", "tx=commit")] [("Prefer", "tx=commit")]
@@ -514,102 +458,38 @@ spec = do
}|] }|]
{ matchStatus = 400 } { matchStatus = 400 }
it "works with views with an explicit order by unique col" $ do it "works with views with an explicit order by unique col" $
get "/limited_update_items_view" baseTable "limited_update_items_view" "id" tblDataBefore
`shouldRespondWith` `mutatesWith`
[json|[ requestMutation methodPatch "/limited_update_items_view?order=id&limit=1&offset=1"
{ "id": 1, "name": "item-1" }
, { "id": 2, "name": "item-2" }
, { "id": 3, "name": "item-3" }
]|]
request methodPatch "/limited_update_items_view?order=id&limit=1&offset=1"
[("Prefer", "tx=commit")]
[json| {"name": "updated-item"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items_view?order=id"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 2, "name": "updated-item" } , { "id": 2, "name": "updated-item" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works with views with an explicit order by composite pk" $
[("Prefer", "tx=commit")] baseTable "limited_update_items_cpk_view" "id" tblDataBefore
[json| {"tbl_name": "limited_update_items_view"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodPatch "/limited_update_items_cpk_view?order=id,name&limit=1&offset=1"
{ 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"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items_cpk_view?order=id,name"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "item-1" } { "id": 1, "name": "item-1" }
, { "id": 2, "name": "updated-item" } , { "id": 2, "name": "updated-item" }
, { "id": 3, "name": "item-3" } , { "id": 3, "name": "item-3" }
]|] ]|]
request methodPost "/rpc/reset_items_tables" it "works on a table without a pk by ordering by 'ctid'" $
[("Prefer", "tx=commit")] baseTable "limited_update_items_no_pk" "id" tblDataBefore
[json| {"tbl_name": "limited_update_items_cpk_view"} |] `mutatesWith`
`shouldRespondWith` "" requestMutation methodPatch "/limited_update_items_no_pk?order=ctid&limit=1"
{ 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"} |] [json| {"name": "updated-item"} |]
`shouldRespondWith` `shouldMutateInto`
""
{ matchStatus = 204
, matchHeaders = [ matchHeaderAbsent hContentType
, "Preference-Applied" <:> "tx=commit" ]
}
get "/limited_update_items_no_pk?order=id"
`shouldRespondWith`
[json|[ [json|[
{ "id": 1, "name": "updated-item" } { "id": 1, "name": "updated-item" }
, { "id": 2, "name": "item-2" } , { "id": 2, "name": "item-2" }
, { "id": 3, "name": "item-3" } , { "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 }
+34 -1
View File
@@ -18,6 +18,7 @@ import Text.Regex.TDFA ((=~))
import Network.HTTP.Types import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import PostgREST.Config (AppConfig (..), import PostgREST.Config (AppConfig (..),
@@ -27,7 +28,7 @@ import PostgREST.Config (AppConfig (..),
parseSecret) parseSecret)
import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..)) import PostgREST.DbStructure.Identifiers (QualifiedIdentifier (..))
import PostgREST.MediaType (MediaType (..)) import PostgREST.MediaType (MediaType (..))
import Protolude hiding (toS) import Protolude hiding (get, toS)
import Protolude.Conv (toS) import Protolude.Conv (toS)
matchContentTypeJson :: MatchHeader matchContentTypeJson :: MatchHeader
@@ -243,3 +244,35 @@ isErrorFormat s =
obj = decode s :: Maybe (M.Map Text Value) obj = decode s :: Maybe (M.Map Text Value)
keys = maybe S.empty M.keysSet obj keys = maybe S.empty M.keysSet obj
validKeys = S.fromList ["message", "details", "hint", "code"] 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 ())
+5 -3
View File
@@ -2531,13 +2531,15 @@ select *, 'static'::text as static from limited_delete_items;
create view limited_delete_items_cpk_view as create view limited_delete_items_cpk_view as
select * from limited_delete_items_cpk; 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( execute format(
$$ $$
delete from %I where true; -- WHERE is required for pg-safeupdate tests 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, $$::text,
tbl_name, tbl_name); tbl_name, tbl_name, tbl_name)
using tbl_data;
end; $_$ language plpgsql volatile; end; $_$ language plpgsql volatile;
-- tables for ensuring we generate real junctions for many-to-many relationships -- tables for ensuring we generate real junctions for many-to-many relationships