From 609c9aead86e492be72785a7b8be01255f529b85 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Thu, 3 Dec 2020 18:54:33 +0100 Subject: [PATCH] Make transaction-rollback=true the default for test-suite (#1663) Change test-suite to use db-tx-rollback-all = true by default --- test/Feature/AndOrParamsSpec.hs | 45 ++++-- test/Feature/DeleteSpec.hs | 8 - test/Feature/EmbedDisambiguationSpec.hs | 6 +- test/Feature/InsertSpec.hs | 89 +++++----- test/Feature/JsonOperatorSpec.hs | 11 +- test/Feature/MultipleSchemaSpec.hs | 64 ++++---- test/Feature/QueryLimitedSpec.hs | 67 ++++---- test/Feature/QuerySpec.hs | 8 +- test/Feature/RangeSpec.hs | 160 ++++++++++-------- test/Feature/RollbackSpec.hs | 9 +- test/Feature/RpcPreRequestGucsSpec.hs | 97 ++++++----- test/Feature/RpcSpec.hs | 42 +++-- test/Feature/SingularSpec.hs | 206 +++++++++++++---------- test/Feature/UnicodeSpec.hs | 19 ++- test/Feature/UpdateSpec.hs | 104 +++++++----- test/Feature/UpsertSpec.hs | 207 +++++++++++++++--------- test/Main.hs | 25 ++- test/SpecHelper.hs | 2 +- test/fixtures/data.sql | 12 ++ test/fixtures/privileges.sql | 2 - test/fixtures/schema.sql | 16 +- 21 files changed, 706 insertions(+), 493 deletions(-) diff --git a/test/Feature/AndOrParamsSpec.hs b/test/Feature/AndOrParamsSpec.hs index f315f1a005..83e33f78fb 100644 --- a/test/Feature/AndOrParamsSpec.hs +++ b/test/Feature/AndOrParamsSpec.hs @@ -33,13 +33,24 @@ spec actualPgVersion = {"id": 1, "child_entities": [ { "id": 1 }, { "id": 2 } ] }, { "id": 2, "child_entities": []}, {"id": 3, "child_entities": []}, {"id": 4, "child_entities": []} ]|] { matchHeaders = [matchContentTypeJson] } + it "can do logic on the third level" $ - get "/entities?child_entities.grandchild_entities.or=(id.eq.1,id.eq.2)&select=id,child_entities(id,grandchild_entities(id))" `shouldRespondWith` - [json|[ - {"id": 1, "child_entities": [ { "id": 1, "grandchild_entities": [ { "id": 1 }, { "id": 2 } ]}, { "id": 2, "grandchild_entities": []}]}, - {"id": 2, "child_entities": [ { "id": 3, "grandchild_entities": []} ]}, - {"id": 3, "child_entities": []}, {"id": 4, "child_entities": []} - ]|] { matchHeaders = [matchContentTypeJson] } + get "/entities?child_entities.grandchild_entities.or=(id.eq.1,id.eq.2)&select=id,child_entities(id,grandchild_entities(id))" + `shouldRespondWith` + [json|[ + {"id": 1, "child_entities": [ + { "id": 1, "grandchild_entities": [ { "id": 1 }, { "id": 2 } ]}, + { "id": 2, "grandchild_entities": []}, + { "id": 4, "grandchild_entities": []}, + { "id": 5, "grandchild_entities": []} + ]}, + {"id": 2, "child_entities": [ + { "id": 3, "grandchild_entities": []}, + { "id": 6, "grandchild_entities": []} + ]}, + {"id": 3, "child_entities": []}, + {"id": 4, "child_entities": []} + ]|] context "and/or params combined" $ do it "can be nested inside the same expression" $ @@ -210,12 +221,15 @@ spec actualPgVersion = context "used with POST" $ it "includes related data with filters" $ request methodPost "/child_entities?select=id,entities(id)&entities.or=(id.eq.2,id.eq.3)&entities.order=id" - [("Prefer", "return=representation")] - [json|[{"id":4,"name":"entity 4","parent_id":1}, - {"id":5,"name":"entity 5","parent_id":2}, - {"id":6,"name":"entity 6","parent_id":3}]|] `shouldRespondWith` - [json|[{"id": 4, "entities":null}, {"id": 5, "entities": {"id": 2}}, {"id": 6, "entities": {"id": 3}}]|] - { matchStatus = 201, matchHeaders = [matchContentTypeJson] } + [("Prefer", "return=representation")] + [json|[ + {"id":7,"name":"entity 4","parent_id":1}, + {"id":8,"name":"entity 5","parent_id":2}, + {"id":9,"name":"entity 6","parent_id":3} + ]|] + `shouldRespondWith` + [json|[{"id": 7, "entities":null}, {"id": 8, "entities": {"id": 2}}, {"id": 9, "entities": {"id": 3}}]|] + { matchStatus = 201 } context "used with PATCH" $ it "succeeds when using and/or params" $ @@ -228,9 +242,10 @@ spec actualPgVersion = context "used with DELETE" $ it "succeeds when using and/or params" $ request methodDelete "/grandchild_entities?or=(id.eq.1,id.eq.2)&select=id,name" - [("Prefer", "return=representation")] "" `shouldRespondWith` - [json|[{ "id": 1, "name" : "updated grandchild entity"},{ "id": 2, "name" : "updated grandchild entity"}]|] - { matchHeaders = [matchContentTypeJson] } + [("Prefer", "return=representation")] + "" + `shouldRespondWith` + [json|[{ "id": 1, "name" : "grandchild entity 1" },{ "id": 2, "name" : "grandchild entity 2" }]|] it "can query columns that begin with and/or reserved words" $ get "/grandchild_entities?or=(and_starting_col.eq.smth, or_starting_col.eq.smth)" `shouldRespondWith` 200 diff --git a/test/Feature/DeleteSpec.hs b/test/Feature/DeleteSpec.hs index db4b0993f3..86bc89842d 100644 --- a/test/Feature/DeleteSpec.hs +++ b/test/Feature/DeleteSpec.hs @@ -57,14 +57,6 @@ spec = , matchHeaders = ["Content-Range" <:> "*/*"] } - it "actually clears items ouf the db" $ do - _ <- request methodDelete "/items?id=lt.15" [] "" - get "/items" - `shouldRespondWith` [json|[{"id":15}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-0/*"] - } - context "known route, no records matched" $ it "includes [] body if return=rep" $ request methodDelete "/items?id=eq.101" diff --git a/test/Feature/EmbedDisambiguationSpec.hs b/test/Feature/EmbedDisambiguationSpec.hs index bb6e2330f0..f2c7635020 100644 --- a/test/Feature/EmbedDisambiguationSpec.hs +++ b/test/Feature/EmbedDisambiguationSpec.hs @@ -194,9 +194,9 @@ spec = { matchHeaders = [matchContentTypeJson] } it "can request two parents with fks" $ - get "/articleStars?select=createdAt,article(owner),user(name)&limit=1" `shouldRespondWith` - [json|[{"createdAt":"2015-12-08T04:22:57.472738","article":{"owner": "postgrest_test_authenticator"},"user":{"name": "Angela Martin"}}]|] - { matchHeaders = [matchContentTypeJson] } + get "/articleStars?select=createdAt,article(id),user(name)&limit=1" + `shouldRespondWith` + [json|[{"createdAt":"2015-12-08T04:22:57.472738","article":{"id": 1},"user":{"name": "Angela Martin"}}]|] it "can specify a view!fk" $ get "/message?select=id,body,sender:person_detail!message_sender_fkey(name,sent),recipient:person_detail!message_recipient_fkey(name,received)&id=lt.4" `shouldRespondWith` diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index d1b3daa409..956e3d9715 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -135,18 +135,21 @@ spec actualPgVersion = do context "with no pk supplied" $ do context "into a table with auto-incrementing pk" $ - it "succeeds with 201 and link" $ do - p <- post "/auto_incrementing_pk" [json| { "non_nullable_string":"not null"} |] - liftIO $ do - simpleBody p `shouldBe` "" - simpleHeaders p `shouldSatisfy` matchHeader hLocation "/auto_incrementing_pk\\?id=eq\\.[0-9]+" - simpleStatus p `shouldBe` created201 - let Just location = lookup hLocation $ simpleHeaders p - r <- get location - let [record] = fromJust (JSON.decode $ simpleBody r :: Maybe [IncPK]) - liftIO $ do - incStr record `shouldBe` "not null" - incNullableStr record `shouldBe` Nothing + it "succeeds with 201 and location header" $ do + -- reset pk sequence first to make test repeatable + request methodPost "/rpc/reset_sequence" + [("Prefer", "tx=commit")] + [json|{"name": "auto_incrementing_pk_id_seq", "value": 2}|] + `shouldRespondWith` + [json|""|] + + post "/auto_incrementing_pk" + [json| { "non_nullable_string":"not null"} |] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = [ "Location" <:> "/auto_incrementing_pk?id=eq.2" ] + } context "into a table with simple pk" $ it "fails with 400 and error" $ @@ -197,21 +200,14 @@ spec actualPgVersion = do context "with compound pk supplied" $ it "builds response location header appropriately" $ do - let inserted = [json| { "k1":12, "k2":"Rock & R+ll" } |] - expectedObj = CompoundPK 12 "Rock & R+ll" Nothing - expectedLoc = "/compound_pk?k1=eq.12&k2=eq.Rock%20%26%20R%2Bll" - p <- request methodPost "/compound_pk" - [("Prefer", "return=representation")] - inserted - liftIO $ do - JSON.decode (simpleBody p) `shouldBe` Just [expectedObj] - simpleStatus p `shouldBe` created201 - lookup hLocation (simpleHeaders p) `shouldBe` Just expectedLoc - - r <- get expectedLoc - liftIO $ do - JSON.decode (simpleBody r) `shouldBe` Just [expectedObj] - simpleStatus r `shouldBe` ok200 + request methodPost "/compound_pk" + [("Prefer", "return=representation")] + [json| { "k1":12, "k2":"Rock & R+ll" } |] + `shouldRespondWith` + [json|[ { "k1":12, "k2":"Rock & R+ll", "extra": null } ]|] + { matchStatus = 201 + , matchHeaders = [ "Location" <:> "/compound_pk?k1=eq.12&k2=eq.Rock%20%26%20R%2Bll" ] + } context "with bulk insert" $ it "returns 201 but no location header" $ do @@ -248,9 +244,9 @@ spec actualPgVersion = do context "attempting to insert a row with the same primary key" $ it "fails returning a 409 Conflict" $ post "/simple_pk" - [json| { "k":"k1", "extra":"e1" } |] + [json| { "k":"xyyx", "extra":"e1" } |] `shouldRespondWith` - [json|{"hint":null,"details":"Key (k)=(k1) already exists.","code":"23505","message":"duplicate key value violates unique constraint \"simple_pk_pkey\""}|] + [json|{"hint":null,"details":"Key (k)=(xyyx) already exists.","code":"23505","message":"duplicate key value violates unique constraint \"simple_pk_pkey\""}|] { matchStatus = 409 } context "attempting to insert a row with conflicting unique constraint" $ @@ -293,16 +289,30 @@ spec actualPgVersion = do , matchHeaders = [] } - it "successfully inserts a row with all-default columns with prefer=rep" $ + it "successfully inserts a row with all-default columns with prefer=rep" $ do + -- reset pk sequence first to make test repeatable + request methodPost "/rpc/reset_sequence" + [("Prefer", "tx=commit")] + [json|{"name": "items_id_seq", "value": 20}|] + `shouldRespondWith` + [json|""|] + request methodPost "/items" [("Prefer", "return=representation")] "{}" `shouldRespondWith` [json|[{ id: 20 }]|] { matchStatus = 201, matchHeaders = [] } - it "successfully inserts a row with all-default columns with prefer=rep and &select=" $ + it "successfully inserts a row with all-default columns with prefer=rep and &select=" $ do + -- reset pk sequence first to make test repeatable + request methodPost "/rpc/reset_sequence" + [("Prefer", "tx=commit")] + [json|{"name": "items_id_seq", "value": 20}|] + `shouldRespondWith` + [json|""|] + request methodPost "/items?select=id" [("Prefer", "return=representation")] "{}" - `shouldRespondWith` [json|[{ id: 21 }]|] + `shouldRespondWith` [json|[{ id: 20 }]|] { matchStatus = 201, matchHeaders = [] } @@ -411,15 +421,22 @@ spec actualPgVersion = do it "succeeds and returns usable location header" $ do let payload = [json| { "k":"圍棋", "extra":"¥" } |] p <- request methodPost "/simple_pk?select=extra,k" - [("Prefer", "return=representation")] - payload + [("Prefer", "tx=commit"), ("Prefer", "return=representation")] + payload liftIO $ do simpleBody p `shouldBe` "["<>payload<>"]" simpleStatus p `shouldBe` created201 let Just location = lookup hLocation $ simpleHeaders p - r <- get (location <> "&select=extra,k") - liftIO $ simpleBody r `shouldBe` "["<>payload<>"]" + get location + `shouldRespondWith` + [json|[ { "k":"圍棋", "extra":"¥" } ]|] + + request methodDelete location + [("Prefer", "tx=commit")] + "" + `shouldRespondWith` + 204 describe "Row level permission" $ it "set user_id when inserting rows" $ do diff --git a/test/Feature/JsonOperatorSpec.hs b/test/Feature/JsonOperatorSpec.hs index 6758a1addd..ba7a0e63ae 100644 --- a/test/Feature/JsonOperatorSpec.hs +++ b/test/Feature/JsonOperatorSpec.hs @@ -202,12 +202,11 @@ spec actualPgVersion = describe "json and jsonb operators" $ do context "Patching record, in a nonempty table" $ it "can set a json column to escaped value" $ do - _ <- post "/json_table" [json| { data: {"escaped":"bar"} } |] - request methodPatch "/json_table?data->>escaped=eq.bar" - [("Prefer", "return=representation")] - [json| { "data": { "escaped":" \"bar" } } |] - `shouldRespondWith` [json| [{ "data": { "escaped":" \"bar" } }] |] - { matchStatus = 200 , matchHeaders = [] } + request methodPatch "/json_table?data->>id=eq.3" + [("Prefer", "return=representation")] + [json| { "data": { "id":" \"escaped" } } |] + `shouldRespondWith` + [json| [{ "data": { "id":" \"escaped" } }] |] when (actualPgVersion >= pgVersion95) $ context "json array negative index" $ do diff --git a/test/Feature/MultipleSchemaSpec.hs b/test/Feature/MultipleSchemaSpec.hs index efc7047263..3355578b1b 100644 --- a/test/Feature/MultipleSchemaSpec.hs +++ b/test/Feature/MultipleSchemaSpec.hs @@ -77,33 +77,37 @@ spec actualPgVersion = context "Inserting tables on different schemas" $ do it "succeeds inserting on default schema and returning it" $ - request methodPost "/children" [("Prefer", "return=representation")] [json|{"name": "child v1-1", "parent_id": 1}|] - `shouldRespondWith` - [json|[{"id":1, "name": "child v1-1", "parent_id": 1}]|] - { - matchStatus = 201 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"] - } + request methodPost "/children" + [("Prefer", "return=representation")] + [json|{"id": 0, "name": "child v1-1", "parent_id": 1}|] + `shouldRespondWith` + [json|[{"id": 0, "name": "child v1-1", "parent_id": 1}]|] + { + matchStatus = 201 + , matchHeaders = ["Content-Profile" <:> "v1"] + } it "succeeds inserting on the v1 schema and returning its parent" $ - request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v1")] - [json|{"name": "child v1-2", "parent_id": 2}|] + request methodPost "/children?select=id,parent(*)" + [("Prefer", "return=representation"), ("Content-Profile", "v1")] + [json|{"id": 0, "name": "child v1-2", "parent_id": 2}|] `shouldRespondWith` - [json|[{"id":2, "parent": {"id": 2, "name": "parent v1-2"}}]|] - { - matchStatus = 201 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v1"] - } + [json|[{"id": 0, "parent": {"id": 2, "name": "parent v1-2"}}]|] + { + matchStatus = 201 + , matchHeaders = ["Content-Profile" <:> "v1"] + } it "succeeds inserting on the v2 schema and returning its parent" $ - request methodPost "/children?select=id,parent(*)" [("Prefer", "return=representation"), ("Content-Profile", "v2")] - [json|{"name": "child v2-3", "parent_id": 3}|] + request methodPost "/children?select=id,parent(*)" + [("Prefer", "return=representation"), ("Content-Profile", "v2")] + [json|{"id": 0, "name": "child v2-3", "parent_id": 3}|] `shouldRespondWith` - [json|[{"id":1, "parent": {"id": 3, "name": "parent v2-3"}}]|] - { - matchStatus = 201 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] - } + [json|[{"id": 0, "parent": {"id": 3, "name": "parent v2-3"}}]|] + { + matchStatus = 201 + , matchHeaders = ["Content-Profile" <:> "v2"] + } it "fails when inserting on an unknown schema" $ request methodPost "/children" [("Content-Profile", "unknown")] @@ -180,18 +184,12 @@ spec actualPgVersion = } it "succeeds on deleting on the v2 schema" $ do - request methodDelete "/children?id=eq.1" [("Content-Profile", "v2"), ("Prefer", "return=representation")] "" - `shouldRespondWith` [json|[{"id": 1, "name": "child v2-1 updated", "parent_id": 3}]|] - { - matchStatus = 200 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] - } - request methodGet "/children?id=eq.1" [("Accept-Profile", "v2")] "" - `shouldRespondWith` "[]" - { - matchStatus = 200 - , matchHeaders = [matchContentTypeJson, "Content-Profile" <:> "v2"] - } + request methodDelete "/children?id=eq.1" + [("Content-Profile", "v2"), ("Prefer", "return=representation")] + "" + `shouldRespondWith` + [json|[{"id": 1, "name": "child v2-3", "parent_id": 3}]|] + { matchHeaders = ["Content-Profile" <:> "v2"] } when (actualPgVersion >= pgVersion96) $ it "succeeds on PUT on the v2 schema" $ diff --git a/test/Feature/QueryLimitedSpec.hs b/test/Feature/QueryLimitedSpec.hs index 79a57e3979..b6c16b1ace 100644 --- a/test/Feature/QueryLimitedSpec.hs +++ b/test/Feature/QueryLimitedSpec.hs @@ -15,59 +15,56 @@ spec :: SpecWith ((), Application) spec = describe "Requesting many items with server limits(max-rows) enabled" $ do it "restricts results" $ - get "/items" - `shouldRespondWith` [json| [{"id":1},{"id":2}] |] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-1/*"] - } + get "/items?order=id" + `shouldRespondWith` + [json| [{"id":1},{"id":2}] |] + { matchHeaders = ["Content-Range" <:> "0-1/*"] } it "respects additional client limiting" $ do - r <- request methodGet "/items" - (rangeHdrs $ ByteRangeFromTo 0 0) "" - liftIO $ do - simpleHeaders r `shouldSatisfy` - matchHeader "Content-Range" "0-0/*" - simpleStatus r `shouldBe` ok200 + request methodGet "/items" + (rangeHdrs $ ByteRangeFromTo 0 0) + "" + `shouldRespondWith` + [json| [{"id":1}] |] + { matchHeaders = ["Content-Range" <:> "0-0/*"] } it "works on all levels" $ get "/users?select=id,tasks(id)&order=id.asc&tasks.order=id.asc" - `shouldRespondWith` [json|[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":5},{"id":6}]}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-1/*"] - } + `shouldRespondWith` + [json|[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":5},{"id":6}]}]|] + { matchHeaders = ["Content-Range" <:> "0-1/*"] } it "succeeds in getting parent embeds despite the limit, see #647" $ get "/tasks?select=id,project:projects(id)&id=gt.5" - `shouldRespondWith` [json|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-1/*"] - } + `shouldRespondWith` + [json|[{"id":6,"project":{"id":3}},{"id":7,"project":{"id":4}}]|] + { matchHeaders = ["Content-Range" <:> "0-1/*"] } it "can offset the parent embed, being consistent with the other embed types" $ get "/tasks?select=id,project:projects(id)&id=gt.5&project.offset=1" - `shouldRespondWith` [json|[{"id":6,"project":null}, {"id":7,"project":null}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-1/*"] - } + `shouldRespondWith` + [json|[{"id":6,"project":null}, {"id":7,"project":null}]|] + { matchHeaders = ["Content-Range" <:> "0-1/*"] } context "count=estimated" $ do it "uses the query planner guess when query rows > maxRows" $ request methodHead "/getallprojects_view" [("Prefer", "count=estimated")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/2019"] - } + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-1/2019"] + } it "gives exact count when query rows <= maxRows" $ request methodHead "/getallprojects_view?id=lt.3" [("Prefer", "count=estimated")] "" - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-1/2"] - } + `shouldRespondWith` + "" + { matchHeaders = ["Content-Range" <:> "0-1/2"] } it "only uses the query planner guess if it's indeed greater than the exact count" $ request methodHead "/get_projects_above_view" [("Prefer", "count=estimated")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/3"] - } + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-1/3"] + } diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 241e5b2a1c..4eba18a7ba 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -35,7 +35,7 @@ spec actualPgVersion = do { matchHeaders = ["Content-Range" <:> "0-0/*"] } it "matches with equality using not operator" $ - get "/items?id=not.eq.5" + get "/items?id=not.eq.5&order=id" `shouldRespondWith` [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |] { matchHeaders = ["Content-Range" <:> "0-13/*"] } @@ -401,9 +401,9 @@ spec actualPgVersion = do get "/materialized_projects?select=*,users(*)" `shouldRespondWith` 200 it "can request two parents" $ - get "/articleStars?select=createdAt,article:articles(owner),user:users(name)&limit=1" `shouldRespondWith` - [json|[{"createdAt":"2015-12-08T04:22:57.472738","article":{"owner": "postgrest_test_authenticator"},"user":{"name": "Angela Martin"}}]|] - { matchHeaders = [matchContentTypeJson] } + get "/articleStars?select=createdAt,article:articles(id),user:users(name)&limit=1" + `shouldRespondWith` + [json|[{"createdAt":"2015-12-08T04:22:57.472738","article":{"id": 1},"user":{"name": "Angela Martin"}}]|] it "can detect relations in views from exposed schema that are based on tables in private schema and have columns renames" $ get "/articles?id=eq.1&select=id,articleStars(users(*))" `shouldRespondWith` diff --git a/test/Feature/RangeSpec.hs b/test/Feature/RangeSpec.hs index c7e2df1fd8..4ce0f6cf50 100644 --- a/test/Feature/RangeSpec.hs +++ b/test/Feature/RangeSpec.hs @@ -33,9 +33,11 @@ spec = do `shouldRespondWith` [json| [] |] {matchHeaders = ["Content-Range" <:> "*/*"]} it "returns range Content-Range with range/*" $ - request methodPost "/rpc/getitemrange" [] defaultRange - `shouldRespondWith` [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |] - { matchHeaders = ["Content-Range" <:> "0-14/*"] } + post "/rpc/getitemrange?order=id" + defaultRange + `shouldRespondWith` + [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}] |] + { matchHeaders = ["Content-Range" <:> "0-14/*"] } context "with range headers" $ do context "of acceptable range" $ do @@ -168,20 +170,16 @@ spec = do } it "succeeds if offset equals 0 as a no-op" $ - get "/items?select=id&offset=0" + get "/items?select=id&offset=0&order=id" `shouldRespondWith` - [json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-14/*"] - } + [json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|] + { matchHeaders = ["Content-Range" <:> "0-14/*"] } it "succeeds if offset is negative as a no-op" $ - get "/items?select=id&offset=-4" + get "/items?select=id&offset=-4&order=id" `shouldRespondWith` - [json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-14/*"] - } + [json|[{"id":1},{"id":2},{"id":3},{"id":4},{"id":5},{"id":6},{"id":7},{"id":8},{"id":9},{"id":10},{"id":11},{"id":12},{"id":13},{"id":14},{"id":15}]|] + { matchHeaders = ["Content-Range" <:> "0-14/*"] } it "fails if limit equals 0" $ get "/items?select=id&limit=0" @@ -199,62 +197,98 @@ spec = do context "when count=planned" $ do it "obtains a filtered range" $ do - request methodGet "/items?select=id&id=gt.8" [("Prefer", "count=planned")] "" - `shouldRespondWith` [json|[{"id":9}, {"id":10}, {"id":11}, {"id":12}, {"id":13}, {"id":14}, {"id":15}]|] - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-6/8"] - } - request methodGet "/child_entities?select=id&id=gt.3" [("Prefer", "count=planned")] "" - `shouldRespondWith` [json|[{"id":4}, {"id":5}, {"id":6}]|] - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-2/4"] - } - request methodGet "/getallprojects_view?select=id&id=lt.3" [("Prefer", "count=planned")] "" - `shouldRespondWith` [json|[{"id":1}, {"id":2}]|] - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/673"] - } + request methodGet "/items?select=id&id=gt.8" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + [json|[{"id":9}, {"id":10}, {"id":11}, {"id":12}, {"id":13}, {"id":14}, {"id":15}]|] + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-6/8"] + } + + request methodGet "/child_entities?select=id&id=gt.3" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + [json|[{"id":4}, {"id":5}, {"id":6}]|] + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-2/4"] + } + + request methodGet "/getallprojects_view?select=id&id=lt.3" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + [json|[{"id":1}, {"id":2}]|] + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-1/673"] + } it "obtains the full range" $ do - request methodHead "/items" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-14/15"] - } - request methodHead "/child_entities" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-5/6"] - } - request methodHead "/getallprojects_view" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-4/2019"] - } + request methodHead "/items" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-14/15"] + } + + request methodHead "/child_entities" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-5/6"] + } + + request methodHead "/getallprojects_view" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-4/2019"] + } it "ignores limit/offset on the planned count" $ do - request methodHead "/items?limit=2&offset=3" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "3-4/15"] - } - request methodHead "/child_entities?limit=2" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/6"] - } - request methodHead "/getallprojects_view?limit=2" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 206 - , matchHeaders = ["Content-Range" <:> "0-1/2019"] - } + request methodHead "/items?limit=2&offset=3" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "3-4/15"] + } + + request methodHead "/child_entities?limit=2" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-1/6"] + } + + request methodHead "/getallprojects_view?limit=2" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 206 + , matchHeaders = ["Content-Range" <:> "0-1/2019"] + } it "works with two levels" $ - request methodHead "/child_entities?select=*,entities(*)" [("Prefer", "count=planned")] "" - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-5/6"] - } + request methodHead "/child_entities?select=*,entities(*)" + [("Prefer", "count=planned")] + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-5/6"] + } context "with range headers" $ do context "of acceptable range" $ do diff --git a/test/Feature/RollbackSpec.hs b/test/Feature/RollbackSpec.hs index 49508245be..227c364471 100644 --- a/test/Feature/RollbackSpec.hs +++ b/test/Feature/RollbackSpec.hs @@ -15,7 +15,7 @@ import SpecHelper -- creates Item to work with for PATCH and DELETE postItem = request methodPost "/items" - [("Prefer", "resolution=ignore-duplicates")] + [("Prefer", "tx=commit"), ("Prefer", "resolution=ignore-duplicates")] [json|{"id":0}|] `shouldRespondWith` "" @@ -23,7 +23,9 @@ postItem = -- removes Items left over from POST, PUT, and PATCH deleteItems = - delete "/items?id=lte.0" + request methodDelete "/items?id=lte.0" + [("Prefer", "tx=commit")] + "" `shouldRespondWith` "" { matchStatus = 204 } @@ -175,9 +177,8 @@ shouldNotPersistMutations reqHeaders respHeaders = do allowed :: SpecWith ((), Application) allowed = describe "tx-allow-override = true" $ do describe "without Prefer tx" $ do - -- TODO: Change this to default to rollback for whole test-suite preferDefault `shouldRespondToReads` withoutPreferenceApplied - preferDefault `shouldPersistMutations` withoutPreferenceApplied + preferDefault `shouldNotPersistMutations` withoutPreferenceApplied describe "Prefer tx=commit" $ do preferCommit `shouldRespondToReads` withPreferenceCommitApplied diff --git a/test/Feature/RpcPreRequestGucsSpec.hs b/test/Feature/RpcPreRequestGucsSpec.hs index 9afc7a9a68..8b1fe6a072 100644 --- a/test/Feature/RpcPreRequestGucsSpec.hs +++ b/test/Feature/RpcPreRequestGucsSpec.hs @@ -11,62 +11,79 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Text.Heredoc -import Protolude hiding (get) +import Protolude hiding (get, put) import SpecHelper spec :: SpecWith ((), Application) spec = describe "GUC headers on all methods via pre-request" $ do it "succeeds setting the headers on POST" $ - request methodPost "/items" [] [json|[{"id": 11111}]|] + post "/items" + [json|[{"id": 11111}]|] `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] - } + { matchStatus = 201 + , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + } it "succeeds setting the headers on GET and HEAD" $ do - request methodGet "/items?id=eq.11111" [("User-Agent", "MSIE 6.0")] mempty - `shouldRespondWith` [json|[{"id": 11111}]|] - {matchHeaders = [ - matchContentTypeJson, - "Cache-Control" <:> "no-cache, no-store, must-revalidate"]} + request methodGet "/items?id=eq.1" + [("User-Agent", "MSIE 6.0")] + "" + `shouldRespondWith` + [json|[{"id": 1}]|] + { matchHeaders = ["Cache-Control" <:> "no-cache, no-store, must-revalidate"] } - request methodHead "/items?id=eq.11111" [("User-Agent", "MSIE 7.0")] mempty - `shouldRespondWith` "" - {matchHeaders = ["Cache-Control" <:> "no-cache, no-store, must-revalidate"]} + request methodHead "/items?id=eq.1" + [("User-Agent", "MSIE 7.0")] + "" + `shouldRespondWith` + "" + { matchHeaders = ["Cache-Control" <:> "no-cache, no-store, must-revalidate"] } - request methodHead "/projects" [("Accept", "text/csv")] mempty - `shouldRespondWith` "" - {matchHeaders = ["Content-Disposition" <:> "attachment; filename=projects.csv"]} + request methodHead "/projects" + [("Accept", "text/csv")] + "" + `shouldRespondWith` + "" + { matchHeaders = ["Content-Disposition" <:> "attachment; filename=projects.csv"] } it "succeeds setting the headers on PATCH" $ - request methodPatch "/items?id=eq.11111" [] [json|[{"id": 11111}]|] - `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] - } + patch "/items?id=eq.1" + [json|[{"id": 11111}]|] + `shouldRespondWith` "" + { matchStatus = 204 + , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + } it "succeeds setting the headers on PUT" $ - request methodPut "/items?id=eq.11111" [] [json|[{"id": 11111}]|] + put "/items?id=eq.1" + [json|[{"id": 1}]|] `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] - } + { matchStatus = 204 + , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + } it "succeeds setting the headers on DELETE" $ - request methodDelete "/items?id=eq.11111" [] mempty - `shouldRespondWith` "" - { matchStatus = 204 - , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] - } + delete "/items?id=eq.1" + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = ["X-Custom-Header" <:> "mykey=myval"] + } it "can override the Content-Type header" $ do - request methodHead "/clients?id=eq.1" [] mempty - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Type" <:> "application/custom+json"] - } - request methodHead "/rpc/getallprojects" [] mempty - `shouldRespondWith` "" - { matchStatus = 200 - , matchHeaders = ["Content-Type" <:> "application/custom+json"] - } + request methodHead "/clients?id=eq.1" + [] + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/custom+json"] + } + request methodHead "/rpc/getallprojects" + [] + "" + `shouldRespondWith` + "" + { matchStatus = 200 + , matchHeaders = ["Content-Type" <:> "application/custom+json"] + } diff --git a/test/Feature/RpcSpec.hs b/test/Feature/RpcSpec.hs index 1b4ce0a329..b6be576c59 100644 --- a/test/Feature/RpcSpec.hs +++ b/test/Feature/RpcSpec.hs @@ -457,12 +457,24 @@ spec actualPgVersion = `shouldRespondWith` 405 it "executes the proc exactly once per request" $ do - post "/rpc/callcounter" [json| {} |] `shouldRespondWith` - [json|1|] - { matchHeaders = [matchContentTypeJson] } - post "/rpc/callcounter" [json| {} |] `shouldRespondWith` - [json|2|] - { matchHeaders = [matchContentTypeJson] } + -- callcounter is persistent even with rollback, because it uses a sequence + -- reset counter first to make test repeatable + request methodPost "/rpc/reset_sequence" + [("Prefer", "tx=commit")] + [json|{"name": "callcounter_count", "value": 1}|] + `shouldRespondWith` + [json|""|] + + -- now the test + post "/rpc/callcounter" + [json|{}|] + `shouldRespondWith` + [json|1|] + + post "/rpc/callcounter" + [json|{}|] + `shouldRespondWith` + [json|2|] context "a proc that receives no parameters" $ do it "interprets empty string as empty json object on a post request" $ @@ -891,11 +903,13 @@ spec actualPgVersion = "Set-Cookie" <:> "id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly"]} it "can override the Location header on a trigger" $ - request methodPost "/stuff" [] [json|[{"id": 1, "name": "stuff 1"}]|] - `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = ["Location" <:> "/stuff?id=eq.1&overriden=true"] - } + post "/stuff" + [json|[{"id": 2, "name": "stuff 2"}]|] + `shouldRespondWith` + "" + { matchStatus = 201 + , matchHeaders = ["Location" <:> "/stuff?id=eq.2&overriden=true"] + } -- On https://github.com/PostgREST/postgrest/issues/1427#issuecomment-595907535 -- it was reported that blank headers ` : ` where added and that cause proxies to fail the requests. @@ -935,8 +949,10 @@ spec actualPgVersion = } it "can override the status through trigger" $ - request methodPatch "/stuff?id=eq.1" [] [json|[{"name": "updated stuff 1"}]|] - `shouldRespondWith` 205 + patch "/stuff?id=eq.1" + [json|[{"name": "updated stuff 1"}]|] + `shouldRespondWith` + 205 it "fails when setting invalid status guc" $ get "/rpc/send_bad_status" diff --git a/test/Feature/SingularSpec.hs b/test/Feature/SingularSpec.hs index 0edd7e542d..e80ba755f3 100644 --- a/test/Feature/SingularSpec.hs +++ b/test/Feature/SingularSpec.hs @@ -15,8 +15,7 @@ import SpecHelper spec :: SpecWith ((), Application) spec = describe "Requesting singular json object" $ do - let pgrstObj = "application/vnd.pgrst.object+json" - singular = ("Accept", pgrstObj) + let singular = ("Accept", "application/vnd.pgrst.object+json") context "with GET request" $ do it "fails for zero rows" $ @@ -49,48 +48,52 @@ spec = context "when updating rows" $ do it "works for one row with return=rep" $ do - post "/addresses" [json| { id: 97, address: "A Street" } |] - request methodPatch "/addresses?id=eq.97" - [("Prefer", "return=representation"), singular] - [json| { address: "B Street" } |] + request methodPatch "/addresses?id=eq.1" + [("Prefer", "return=representation"), singular] + [json| { address: "B Street" } |] `shouldRespondWith` - [json|{"id":97,"address":"B Street"}|] + [json|{"id":1,"address":"B Street"}|] { matchHeaders = [matchContentTypeSingular] } it "works for one row with return=minimal" $ - request methodPatch - "/addresses?id=eq.97" - [("Prefer", "return=minimal"), singular] - [json| { address: "C Street" } |] + request methodPatch "/addresses?id=eq.1" + [("Prefer", "return=minimal"), singular] + [json| { address: "C Street" } |] `shouldRespondWith` "" { matchStatus = 204 } it "raises an error for multiple rows" $ do - _ <- post "/addresses" [json| { id: 98, address: "xxx" } |] - _ <- post "/addresses" [json| { id: 99, address: "yyy" } |] - p <- request methodPatch "/addresses?id=gt.0" - [singular] - [json| { address: "zzz" } |] - liftIO $ do - simpleStatus p `shouldBe` notAcceptable406 - isErrorFormat (simpleBody p) `shouldBe` True + request methodPatch "/addresses" + [("Prefer", "tx=commit"), singular] + [json| { address: "zzz" } |] + `shouldRespondWith` + [json|{"details":"Results contain 4 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } -- the rows should not be updated, either - get "/addresses?id=eq.98" `shouldRespondWith` [json|[{"id":98,"address":"xxx"}]|] + get "/addresses?id=eq.1" + `shouldRespondWith` + [json|[{"id":1,"address":"address 1"}]|] it "raises an error for multiple rows with return=rep" $ do - _ <- post "/addresses" [json| { id: 100, address: "xxx" } |] - _ <- post "/addresses" [json| { id: 101, address: "yyy" } |] - p <- request methodPatch "/addresses?id=gt.0" - [("Prefer", "return=representation"), singular] - [json| { address: "zzz" } |] - liftIO $ do - simpleStatus p `shouldBe` notAcceptable406 - isErrorFormat (simpleBody p) `shouldBe` True + request methodPatch "/addresses" + [("Prefer", "tx=commit"), ("Prefer", "return=representation"), singular] + [json| { address: "zzz" } |] + `shouldRespondWith` + [json|{"details":"Results contain 4 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } -- the rows should not be updated, either - get "/addresses?id=eq.100" `shouldRespondWith` [json|[{"id":100,"address":"xxx"}]|] + get "/addresses?id=eq.1" + `shouldRespondWith` + [json|[{"id":1,"address":"address 1"}]|] it "raises an error for zero rows" $ request methodPatch "/items?id=gt.0&id=lt.0" @@ -123,51 +126,60 @@ spec = it "works for one row with return=minimal" $ do request methodPost "/addresses" - [("Prefer", "return=minimal"), singular] - [json| [ { id: 103, address: "xxx" } ] |] + [("Prefer", "return=minimal"), singular] + [json| [ { id: 103, address: "xxx" } ] |] `shouldRespondWith` "" - { matchStatus = 201 - , matchHeaders = ["Content-Range" <:> "*/*"] - } - -- and the element should exist - get "/addresses?id=eq.103" - `shouldRespondWith` [json|[{"id":103,"address":"xxx"}]|] - { matchStatus = 200 - , matchHeaders = [] - } + { matchStatus = 201 + , matchHeaders = ["Content-Range" <:> "*/*"] + } it "raises an error when attempting to create multiple entities" $ do - p <- request methodPost - "/addresses" - [singular] - [json| [ { id: 200, address: "xxx" }, { id: 201, address: "yyy" } ] |] - liftIO $ simpleStatus p `shouldBe` notAcceptable406 + request methodPost "/addresses" + [("Prefer", "tx=commit"), singular] + [json| [ { id: 200, address: "xxx" }, { id: 201, address: "yyy" } ] |] + `shouldRespondWith` + [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } -- the rows should not exist, either - get "/addresses?id=eq.200" `shouldRespondWith` "[]" + get "/addresses?id=eq.200" + `shouldRespondWith` + "[]" it "raises an error when attempting to create multiple entities with return=rep" $ do - p <- request methodPost - "/addresses" - [("Prefer", "return=representation"), singular] - [json| [ { id: 202, address: "xxx" }, { id: 203, address: "yyy" } ] |] - liftIO $ simpleStatus p `shouldBe` notAcceptable406 + request methodPost "/addresses" + [("Prefer", "tx=commit"), ("Prefer", "return=representation"), singular] + [json| [ { id: 202, address: "xxx" }, { id: 203, address: "yyy" } ] |] + `shouldRespondWith` + [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } -- the rows should not exist, either - get "/addresses?id=eq.202" `shouldRespondWith` "[]" + get "/addresses?id=eq.202" + `shouldRespondWith` + "[]" it "raises an error regardless of return=minimal" $ do request methodPost "/addresses" - [("Prefer", "return=minimal"), singular] - [json| [ { id: 204, address: "xxx" }, { id: 205, address: "yyy" } ] |] + [("Prefer", "tx=commit"), ("Prefer", "return=minimal"), singular] + [json| [ { id: 204, address: "xxx" }, { id: 205, address: "yyy" } ] |] `shouldRespondWith` - [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] - { matchStatus = 406 - , matchHeaders = [matchContentTypeSingular] - } + [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } -- the rows should not exist, either - get "/addresses?id=eq.204" `shouldRespondWith` "[]" + get "/addresses?id=eq.204" + `shouldRespondWith` + "[]" it "raises an error when creating zero entities" $ request methodPost "/addresses" @@ -203,28 +215,40 @@ spec = liftIO $ simpleBody p `shouldBe` "" it "raises an error when attempting to delete multiple entities" $ do - let firstItems = "/items?id=gt.0&id=lt.6" - request methodDelete firstItems - [singular] "" - `shouldRespondWith` 406 + request methodDelete "/items?id=gt.0&id=lt.6" + [("Prefer", "tx=commit"), singular] + "" + `shouldRespondWith` + [json|{"details":"Results contain 5 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } - get firstItems - `shouldRespondWith` [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}] |] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-4/*"] - } + -- the rows should still exist + get "/items?id=gt.0&id=lt.6&order=id" + `shouldRespondWith` + [json| [{"id":1},{"id":2},{"id":3},{"id":4},{"id":5}] |] + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-4/*"] + } it "raises an error when attempting to delete multiple entities with return=rep" $ do - let firstItems = "/items?id=gt.5&id=lt.11" - request methodDelete firstItems - [("Prefer", "return=representation"), singular] "" - `shouldRespondWith` 406 + request methodDelete "/items?id=gt.5&id=lt.11" + [("Prefer", "tx=commit"), ("Prefer", "return=representation"), singular] "" + `shouldRespondWith` + [json|{"details":"Results contain 5 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } - get firstItems + -- the rows should still exist + get "/items?id=gt.5&id=lt.11" `shouldRespondWith` [json| [{"id":6},{"id":7},{"id":8},{"id":9},{"id":10}] |] - { matchStatus = 200 - , matchHeaders = ["Content-Range" <:> "0-4/*"] - } + { matchStatus = 200 + , matchHeaders = ["Content-Range" <:> "0-4/*"] + } it "raises an error when deleting zero entities" $ request methodDelete "/items?id=lt.0" @@ -277,18 +301,24 @@ spec = , matchHeaders = [matchContentTypeSingular] } - it "executes the proc exactly once per request" $ do - request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |] - `shouldRespondWith` [json|[{"id":1,"name":"Windows 7"}]|] + it "fails for multiple rows with rolled back changes" $ do + post "/rpc/getproject?select=id,name" + [json| {"id": 1} |] + `shouldRespondWith` + [json|[{"id":1,"name":"Windows 7"}]|] - request methodPost "/rpc/setprojects" [singular] - [json| {"id_l": 1, "id_h": 2, "name": "changed"} |] + request methodPost "/rpc/setprojects" + [("Prefer", "tx=commit"), singular] + [json| {"id_l": 1, "id_h": 2, "name": "changed"} |] `shouldRespondWith` - [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] - { matchStatus = 406 - , matchHeaders = [matchContentTypeSingular] - } + [json|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] + { matchStatus = 406 + , matchHeaders = [ matchContentTypeSingular + , "Preference-Applied" <:> "tx=commit" ] + } - -- should not actually have executed the function - request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |] - `shouldRespondWith` [json|[{"id":1,"name":"Windows 7"}]|] + -- should rollback function + post "/rpc/getproject?select=id,name" + [json| {"id": 1} |] + `shouldRespondWith` + [json|[{"id":1,"name":"Windows 7"}]|] diff --git a/test/Feature/UnicodeSpec.hs b/test/Feature/UnicodeSpec.hs index b8bd2149ed..5241a62711 100644 --- a/test/Feature/UnicodeSpec.hs +++ b/test/Feature/UnicodeSpec.hs @@ -2,6 +2,7 @@ module Feature.UnicodeSpec where import Network.Wai (Application) +import Network.HTTP.Types import Test.Hspec import Test.Hspec.Wai import Test.Hspec.Wai.JSON @@ -16,9 +17,19 @@ spec = get "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" `shouldRespondWith` "[]" - void $ post "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" - [json| { "هویت": 1 } |] + request methodPost "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" + [("Prefer", "tx=commit"), ("Prefer", "return=representation")] + [json| { "هویت": 1 } |] + `shouldRespondWith` + [json| [{ "هویت": 1 }] |] + { matchStatus = 201 } get "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" - `shouldRespondWith` [json| [{ "هویت": 1 }] |] - { matchHeaders = [matchContentTypeJson] } + `shouldRespondWith` + [json| [{ "هویت": 1 }] |] + + request methodDelete "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF" + [("Prefer", "tx=commit")] + "" + `shouldRespondWith` + 204 diff --git a/test/Feature/UpdateSpec.hs b/test/Feature/UpdateSpec.hs index 0d09f097c8..60888eef04 100644 --- a/test/Feature/UpdateSpec.hs +++ b/test/Feature/UpdateSpec.hs @@ -51,22 +51,31 @@ spec = do context "in a nonempty table" $ do it "can update a single item" $ do - g <- get "/items?id=eq.42" - liftIO $ simpleHeaders g - `shouldSatisfy` matchHeader "Content-Range" "\\*/\\*" - p <- request methodPatch "/items?id=eq.2" [] [json| { "id":42 } |] - pure p `shouldRespondWith` "" - { matchStatus = 204, - matchHeaders = ["Content-Range" <:> "0-0/*"] - } - liftIO $ lookup hContentType (simpleHeaders p) `shouldBe` Nothing + get "/items?id=eq.42" + `shouldRespondWith` + [json|[]|] + + request methodPatch "/items?id=eq.2" + [("Prefer", "tx=commit")] + [json| { "id":42 } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = ["Content-Range" <:> "0-0/*" + , "Preference-Applied" <:> "tx=commit" ] + } -- check it really got updated - g' <- get "/items?id=eq.42" - liftIO $ simpleHeaders g' - `shouldSatisfy` matchHeader "Content-Range" "0-0/\\*" + get "/items?id=eq.42" + `shouldRespondWith` + [json|[ { "id": 42 } ]|] + -- put value back for other tests - void $ request methodPatch "/items?id=eq.42" [] [json| { "id":2 } |] + request methodPatch "/items?id=eq.42" + [("Prefer", "tx=commit")] + [json| { "id":2 } |] + `shouldRespondWith` + 204 it "returns empty array when no rows updated and return=rep" $ request methodPatch "/items?id=eq.999999" @@ -91,23 +100,38 @@ spec = do } it "can update multiple items" $ do - replicateM_ 10 $ post "/auto_incrementing_pk" - [json| { non_nullable_string: "a" } |] - replicateM_ 10 $ post "/auto_incrementing_pk" - [json| { non_nullable_string: "b" } |] - _ <- request methodPatch - "/auto_incrementing_pk?non_nullable_string=eq.a" [] - [json| { non_nullable_string: "c" } |] - g <- get "/auto_incrementing_pk?non_nullable_string=eq.c" - liftIO $ simpleHeaders g - `shouldSatisfy` matchHeader "Content-Range" "0-9/\\*" + get "/no_pk?select=a&b=eq.1" + `shouldRespondWith` + [json|[]|] + + request methodPatch "/no_pk?b=eq.0" + [("Prefer", "tx=commit")] + [json| { b: "1" } |] + `shouldRespondWith` + "" + { matchStatus = 204 + , matchHeaders = ["Content-Range" <:> "0-1/*" + , "Preference-Applied" <:> "tx=commit" ] + } + + -- check it really got updated + get "/no_pk?select=a&b=eq.1" + `shouldRespondWith` + [json|[ { a: "1" }, { a: "2" } ]|] + + -- put value back for other tests + request methodPatch "/no_pk?b=eq.1" + [("Prefer", "tx=commit")] + [json| { b: "0" } |] + `shouldRespondWith` + 204 it "can set a column to NULL" $ do - _ <- post "/no_pk" [json| { a: "keepme", b: "nullme" } |] - _ <- request methodPatch "/no_pk?b=eq.nullme" [] [json| { b: null } |] - get "/no_pk?a=eq.keepme" `shouldRespondWith` - [json| [{ a: "keepme", b: null }] |] - { matchHeaders = [matchContentTypeJson] } + request methodPatch "/no_pk?a=eq.1" + [("Prefer", "return=representation")] + [json| { b: null } |] + `shouldRespondWith` + [json| [{ a: "1", b: null }] |] context "filtering by a computed column" $ do it "is successful" $ @@ -250,23 +274,19 @@ spec = do context "with unicode values" $ it "succeeds and returns values intact" $ do - void $ request methodPost "/no_pk" [] - [json| { "a":"patchme", "b":"patchme" } |] - let payload = [json| { "a":"圍棋", "b":"¥" } |] - p <- request methodPatch "/no_pk?a=eq.patchme&b=eq.patchme" - [("Prefer", "return=representation")] payload - liftIO $ do - simpleBody p `shouldBe` "["<>payload<>"]" - simpleStatus p `shouldBe` ok200 + request methodPatch "/no_pk?a=eq.1" + [("Prefer", "return=representation")] + [json| { "a":"圍棋", "b":"¥" } |] + `shouldRespondWith` + [json|[ { "a":"圍棋", "b":"¥" } ]|] context "PATCH with ?columns parameter" $ do it "ignores json keys not included in ?columns" $ do - post "/articles?columns=id,body" [json| {"id": 200} |] - request methodPatch "/articles?id=eq.200&columns=body" [("Prefer", "return=representation")] - [json| {"body": "Some real content", "smth": "here", "other": "stuff", "fake_id": 13} |] `shouldRespondWith` - [json|[{"id": 200, "body": "Some real content", "owner": "postgrest_test_anonymous"}]|] - { matchStatus = 200 - , matchHeaders = [] } + request methodPatch "/articles?id=eq.1&columns=body" + [("Prefer", "return=representation")] + [json| {"body": "Some real content", "smth": "here", "other": "stuff", "fake_id": 13} |] + `shouldRespondWith` + [json|[{"id": 1, "body": "Some real content", "owner": "postgrest_test_anonymous"}]|] it "ignores json keys and gives 404 if no record updated" $ request methodPatch "/articles?id=eq.2001&columns=body" [("Prefer", "return=representation")] diff --git a/test/Feature/UpsertSpec.hs b/test/Feature/UpsertSpec.hs index a771293989..0eaad6b371 100644 --- a/test/Feature/UpsertSpec.hs +++ b/test/Feature/UpsertSpec.hs @@ -100,30 +100,38 @@ spec = } it "INSERTs and ignores rows on single unique key conflict" $ - request methodPost "/single_unique?on_conflict=unique_key" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] - [json| [ - { "unique_key": 1, "value": "B" }, - { "unique_key": 2, "value": "C" }, - { "unique_key": 3, "value": "D" } - ]|] `shouldRespondWith` [json| [ - { "unique_key": 3, "value": "D" } - ]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson] - } + request methodPost "/single_unique?on_conflict=unique_key" + [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] + [json| [ + { "unique_key": 1, "value": "B" }, + { "unique_key": 2, "value": "C" }, + { "unique_key": 3, "value": "D" } + ]|] + `shouldRespondWith` + [json| [ + { "unique_key": 2, "value": "C" }, + { "unique_key": 3, "value": "D" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"] + } it "INSERTs and UPDATEs rows on compound unique keys conflict" $ - request methodPost "/compound_unique?on_conflict=key1,key2" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] - [json| [ - { "key1": 1, "key2": 1, "value": "B" }, - { "key1": 1, "key2": 2, "value": "C" }, - { "key1": 1, "key2": 3, "value": "D" } - ]|] `shouldRespondWith` [json| [ - { "key1": 1, "key2": 3, "value": "D" } - ]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson] - } + request methodPost "/compound_unique?on_conflict=key1,key2" + [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] + [json| [ + { "key1": 1, "key2": 1, "value": "B" }, + { "key1": 1, "key2": 2, "value": "C" }, + { "key1": 1, "key2": 3, "value": "D" } + ]|] + `shouldRespondWith` + [json| [ + { "key1": 1, "key2": 2, "value": "C" }, + { "key1": 1, "key2": 3, "value": "D" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"] + } it "succeeds if the table has only PK cols and no other cols" $ do request methodPost "/only_pk" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] @@ -233,58 +241,93 @@ spec = context "Inserting row" $ do it "succeeds on table with single pk col" $ do - get "/tiobe_pls?name=eq.Go" `shouldRespondWith` "[]" - put "/tiobe_pls?name=eq.Go" [json| [ { "name": "Go", "rank": 19 } ]|] `shouldRespondWith` 204 - get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json| [ { "name": "Go", "rank": 19 } ]|] { matchHeaders = [matchContentTypeJson] } + -- assert that the next request will indeed be an insert + get "/tiobe_pls?name=eq.Go" + `shouldRespondWith` + [json|[]|] + + request methodPut "/tiobe_pls?name=eq.Go" + [("Prefer", "return=representation")] + [json| [ { "name": "Go", "rank": 19 } ]|] + `shouldRespondWith` + [json| [ { "name": "Go", "rank": 19 } ]|] it "succeeds on table with composite pk" $ do + -- assert that the next request will indeed be an insert get "/employees?first_name=eq.Susan&last_name=eq.Heidt" - `shouldRespondWith` "[]" - put "/employees?first_name=eq.Susan&last_name=eq.Heidt" - [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] - `shouldRespondWith` 204 - get "/employees?first_name=eq.Susan&last_name=eq.Heidt" `shouldRespondWith` - [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] - { matchHeaders = [matchContentTypeJson] } + [json|[]|] + + request methodPut "/employees?first_name=eq.Susan&last_name=eq.Heidt" + [("Prefer", "return=representation")] + [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] + `shouldRespondWith` + [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] it "succeeds if the table has only PK cols and no other cols" $ do - get "/only_pk?id=eq.10" `shouldRespondWith` "[]" - put "/only_pk?id=eq.10" [json|[ { "id": 10 } ]|] `shouldRespondWith` 204 - get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] } + -- assert that the next request will indeed be an insert + get "/only_pk?id=eq.10" + `shouldRespondWith` + [json|[]|] + + request methodPut "/only_pk?id=eq.10" + [("Prefer", "return=representation")] + [json|[ { "id": 10 } ]|] + `shouldRespondWith` + [json|[ { "id": 10 } ]|] context "Updating row" $ do it "succeeds on table with single pk col" $ do - get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json|[ { "name": "Go", "rank": 19 } ]|] { matchHeaders = [matchContentTypeJson] } - put "/tiobe_pls?name=eq.Go" [json| [ { "name": "Go", "rank": 13 } ]|] `shouldRespondWith` 204 - get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json| [ { "name": "Go", "rank": 13 } ]|] { matchHeaders = [matchContentTypeJson] } + -- assert that the next request will indeed be an update + get "/tiobe_pls?name=eq.Java" + `shouldRespondWith` + [json|[ { "name": "Java", "rank": 1 } ]|] - it "succeeds if the payload has more than one row, but it only puts the first element" $ - request methodPut "/tiobe_pls?name=eq.Go" - [("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")] - [json| [ { "name": "Go", "rank": 19 }, { "name": "Swift", "rank": 12 } ] |] + request methodPut "/tiobe_pls?name=eq.Java" + [("Prefer", "return=representation")] + [json| [ { "name": "Java", "rank": 13 } ]|] + `shouldRespondWith` + [json| [ { "name": "Java", "rank": 13 } ]|] + + -- TODO: move this to SingularSpec? + it "succeeds if the payload has more than one row, but it only puts the first element" $ do + -- assert that the next request will indeed be an update + get "/tiobe_pls?name=eq.Java" `shouldRespondWith` - [json|{ "name": "Go", "rank": 19 }|] - { matchStatus = 200 , matchHeaders = [matchContentTypeSingular] } + [json|[ { "name": "Java", "rank": 1 } ]|] + + request methodPut "/tiobe_pls?name=eq.Java" + [("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")] + [json| [ { "name": "Java", "rank": 19 }, { "name": "Swift", "rank": 12 } ] |] + `shouldRespondWith` + [json|{ "name": "Java", "rank": 19 }|] + { matchHeaders = [matchContentTypeSingular] } it "succeeds on table with composite pk" $ do - get "/employees?first_name=eq.Susan&last_name=eq.Heidt" + -- assert that the next request will indeed be an update + get "/employees?first_name=eq.Frances M.&last_name=eq.Roe" `shouldRespondWith` - [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] - { matchHeaders = [matchContentTypeJson] } - put "/employees?first_name=eq.Susan&last_name=eq.Heidt" - [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "60000", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] - `shouldRespondWith` 204 - get "/employees?first_name=eq.Susan&last_name=eq.Heidt" + [json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$24,000.00", "company": "One-Up Realty", "occupation": "Author" } ]|] + + request methodPut "/employees?first_name=eq.Frances M.&last_name=eq.Roe" + [("Prefer", "return=representation")] + [json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "60000", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] `shouldRespondWith` - [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] - { matchHeaders = [matchContentTypeJson] } + [json| [ { "first_name": "Frances M.", "last_name": "Roe", "salary": "$60,000.00", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] it "succeeds if the table has only PK cols and no other cols" $ do - get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] } - put "/only_pk?id=eq.10" [json|[ { "id": 10 } ]|] `shouldRespondWith` 204 - get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] } + -- assert that the next request will indeed be an update + get "/only_pk?id=eq.1" + `shouldRespondWith` + [json|[ { "id": 1 } ]|] + request methodPut "/only_pk?id=eq.1" + [("Prefer", "return=representation")] + [json|[ { "id": 1 } ]|] + `shouldRespondWith` + [json|[ { "id": 1 } ]|] + + -- TODO: move this to SingularSpec? it "works with return=representation and vnd.pgrst.object+json" $ request methodPut "/tiobe_pls?name=eq.Ruby" [("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")] @@ -292,28 +335,38 @@ spec = `shouldRespondWith` [json|{ "name": "Ruby", "rank": 11 }|] { matchHeaders = [matchContentTypeSingular] } context "with a camel case pk column" $ do - it "works with POST and merge-duplicates/ignore-duplicates headers" $ do - request methodPost "/UnitTest" [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] - [json| [ - { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, - { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } - ]|] `shouldRespondWith` [json|[ - { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, - { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } - ]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates", matchContentTypeJson] - } - request methodPost "/UnitTest" [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] - [json| [ - { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, - { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } - ]|] `shouldRespondWith` [json|[]|] - { matchStatus = 201 - , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates", matchContentTypeJson] - } + it "works with POST and merge-duplicates" $ do + request methodPost "/UnitTest" + [("Prefer", "return=representation"), ("Prefer", "resolution=merge-duplicates")] + [json|[ + { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, + { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } + ]|] + `shouldRespondWith` + [json|[ + { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, + { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=merge-duplicates"] + } + + it "works with POST and ignore-duplicates headers" $ do + request methodPost "/UnitTest" + [("Prefer", "return=representation"), ("Prefer", "resolution=ignore-duplicates")] + [json|[ + { "idUnitTest": 1, "nameUnitTest": "name of unittest 1" }, + { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } + ]|] + `shouldRespondWith` + [json|[ + { "idUnitTest": 2, "nameUnitTest": "name of unittest 2" } + ]|] + { matchStatus = 201 + , matchHeaders = ["Preference-Applied" <:> "resolution=ignore-duplicates"] + } it "works with PUT" $ do put "/UnitTest?idUnitTest=eq.1" [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] `shouldRespondWith` 204 get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith` - [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] { matchHeaders = [matchContentTypeJson] } + [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] diff --git a/test/Main.hs b/test/Main.hs index 7874c75f69..5bbbdbf230 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -96,39 +96,32 @@ main = do nonexistentSchemaApp = appDbs testNonexistentSchemaCfg multipleSchemaApp = appDbs testMultipleSchemaCfg - let reset, analyze :: IO () - reset = resetDb testDbConn + let analyze :: IO () analyze = do analyzeTable testDbConn "items" analyzeTable testDbConn "child_entities" specs = uncurry describe <$> [ - ("Feature.AuthSpec" , Feature.AuthSpec.spec actualPgVersion) - , ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec) + ("Feature.AndOrParamsSpec" , Feature.AndOrParamsSpec.spec actualPgVersion) + , ("Feature.AuthSpec" , Feature.AuthSpec.spec actualPgVersion) , ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec) , ("Feature.CorsSpec" , Feature.CorsSpec.spec) + , ("Feature.DeleteSpec" , Feature.DeleteSpec.spec) + , ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec) + , ("Feature.InsertSpec" , Feature.InsertSpec.spec actualPgVersion) , ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion) , ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec) , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec) , ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion) - , ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec) + , ("Feature.RawOutputTypesSpec" , Feature.RawOutputTypesSpec.spec) , ("Feature.RollbackAllowedSpec" , Feature.RollbackSpec.allowed) , ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion) - , ("Feature.AndOrParamsSpec" , Feature.AndOrParamsSpec.spec actualPgVersion) + , ("Feature.SingularSpec" , Feature.SingularSpec.spec) + , ("Feature.UpdateSpec" , Feature.UpdateSpec.spec) , ("Feature.UpsertSpec" , Feature.UpsertSpec.spec) ] - mutSpecs = uncurry describe <$> [ - ("Feature.DeleteSpec" , Feature.DeleteSpec.spec) - , ("Feature.InsertSpec" , Feature.InsertSpec.spec actualPgVersion) - , ("Feature.SingularSpec" , Feature.SingularSpec.spec) - , ("Feature.UpdateSpec" , Feature.UpdateSpec.spec) - ] - hspec $ do - -- Only certain Specs need a database reset, this should be used with care as it slows down the whole test suite. - mapM_ (afterAll_ reset . before withApp) mutSpecs - mapM_ (before withApp) specs -- we analyze to get accurate results from EXPLAIN diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index 72d2657278..4515990d71 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -90,7 +90,7 @@ _baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in , configRawMediaTypes = [] , configJWKS = parseSecret <$> secret , configLogLevel = LogCrit - , configTxRollbackAll = False + , configTxRollbackAll = True , configTxAllowOverride = True , configDbPrepared = True } diff --git a/test/fixtures/data.sql b/test/fixtures/data.sql index 85a7cf702f..8762b39d8d 100644 --- a/test/fixtures/data.sql +++ b/test/fixtures/data.sql @@ -359,6 +359,9 @@ TRUNCATE TABLE child_entities CASCADE; INSERT INTO child_entities VALUES (1, 'child entity 1', 1); INSERT INTO child_entities VALUES (2, 'child entity 2', 1); INSERT INTO child_entities VALUES (3, 'child entity 3', 2); +INSERT INTO child_entities VALUES (4, 'child entity 4', 1); +INSERT INTO child_entities VALUES (5, 'child entity 5', 1); +INSERT INTO child_entities VALUES (6, 'child entity 6', 2); TRUNCATE TABLE grandchild_entities CASCADE; INSERT INTO grandchild_entities VALUES (1, 'grandchild entity 1', 1, null, null, null); @@ -626,8 +629,17 @@ INSERT INTO unit_workdays VALUES(1, '2019-12-02', 1, 1, 2, 3); TRUNCATE TABLE v1.parents CASCADE; INSERT INTO v1.parents VALUES(1, 'parent v1-1'), (2, 'parent v1-2'); +TRUNCATE TABLE v1.children CASCADE; +INSERT INTO v1.children VALUES(1, 'child v1-1', 1), (2, 'child v1-2', 2); + TRUNCATE TABLE v2.parents CASCADE; INSERT INTO v2.parents VALUES(3, 'parent v2-3'), (4, 'parent v2-4'); +TRUNCATE TABLE v2.children CASCADE; +INSERT INTO v2.children VALUES(1, 'child v2-3', 3); + TRUNCATE TABLE v2.another_table CASCADE; INSERT INTO v2.another_table VALUES(5, 'value 5'), (6, 'value 6'); + +TRUNCATE TABLE private.stuff CASCADE; +INSERT INTO private.stuff (id, name) VALUES (1, 'stuff 1'); diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 59dadb0831..8b601cc66f 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -144,8 +144,6 @@ GRANT USAGE ON SEQUENCE , items_id_seq , callcounter_count , leak_id_seq - , v1.children_id_seq - , v2.children_id_seq TO postgrest_test_anonymous; -- Privileges for non anonymous users diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 6573a2700a..b22ce6479f 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -365,6 +365,16 @@ CREATE FUNCTION callcounter() RETURNS bigint SELECT nextval('test.callcounter_count'); $_$; +CREATE FUNCTION reset_sequence(name TEXT, value INTEGER) RETURNS void +SECURITY DEFINER +LANGUAGE plpgsql AS $_$ +BEGIN + EXECUTE FORMAT($exec$ + ALTER SEQUENCE %s RESTART WITH %s + $exec$, name, value); +END +$_$; + -- -- Name: singlejsonparam(json); Type: FUNCTION; Schema: test; Owner: - -- @@ -1825,8 +1835,8 @@ create table v1.parents ( ); create table v1.children ( - id serial primary key -, name text + id int primary key +, name text , parent_id int , constraint parent foreign key(parent_id) references v1.parents(id) @@ -1843,7 +1853,7 @@ create table v2.parents ( ); create table v2.children ( - id serial primary key + id int primary key , name text , parent_id int , constraint parent foreign key(parent_id)