Refactor tests: replace str with json QuasiQuoter where appropriate

This commit is contained in:
Wolfgang Walther
2020-11-20 09:24:32 -05:00
committed by Steve Chavez
parent e08bb3a197
commit 2c52b96e04
9 changed files with 141 additions and 138 deletions
+4 -5
View File
@@ -6,7 +6,6 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import PostgREST.Types (PgVersion, pgVersion112) import PostgREST.Types (PgVersion, pgVersion112)
import Protolude hiding (get) import Protolude hiding (get)
@@ -85,7 +84,7 @@ spec actualPgVersion = describe "authorization" $ do
it "sql functions can read custom and standard claims variables" $ do it "sql functions can read custom and standard claims variables" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmdW4iLCJqdGkiOiJmb28iLCJuYmYiOjEzMDA4MTkzODAsImV4cCI6OTk5OTk5OTk5OSwiaHR0cDovL3Bvc3RncmVzdC5jb20vZm9vIjp0cnVlLCJpc3MiOiJqb2UiLCJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWF0IjoxMzAwODE5MzgwfQ.V5fEpXfpb7feqwVqlcDleFdKu86bdwU2cBRT4fcMhXg" let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmdW4iLCJqdGkiOiJmb28iLCJuYmYiOjEzMDA4MTkzODAsImV4cCI6OTk5OTk5OTk5OSwiaHR0cDovL3Bvc3RncmVzdC5jb20vZm9vIjp0cnVlLCJpc3MiOiJqb2UiLCJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWF0IjoxMzAwODE5MzgwfQ.V5fEpXfpb7feqwVqlcDleFdKu86bdwU2cBRT4fcMhXg"
request methodPost "/rpc/reveal_big_jwt" [auth] "{}" request methodPost "/rpc/reveal_big_jwt" [auth] "{}"
`shouldRespondWith` [str|[{"iss":"joe","sub":"fun","exp":9999999999,"nbf":1300819380,"iat":1300819380,"jti":"foo","http://postgrest.com/foo":true}]|] `shouldRespondWith` [json|[{"iss":"joe","sub":"fun","exp":9999999999,"nbf":1300819380,"iat":1300819380,"jti":"foo","http://postgrest.com/foo":true}]|]
it "allows users with permissions to see their tables" $ do it "allows users with permissions to see their tables" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0" let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0"
@@ -148,7 +147,7 @@ spec actualPgVersion = describe "authorization" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MX0.gKw7qI50i9hMrSJW8BlTpdMEVmMXJYxlAqueGqpa_mE" in let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MX0.gKw7qI50i9hMrSJW8BlTpdMEVmMXJYxlAqueGqpa_mE" in
request methodPost "/rpc/get_current_user" [auth] request methodPost "/rpc/get_current_user" [auth]
[json| {} |] [json| {} |]
`shouldRespondWith` [str|"postgrest_test_author"|] `shouldRespondWith` [json|"postgrest_test_author"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -157,7 +156,7 @@ spec actualPgVersion = describe "authorization" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Mn0.nwzjMI0YLvVGJQTeoCPEBsK983b__gxdpLXisBNaO2A" in let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Mn0.nwzjMI0YLvVGJQTeoCPEBsK983b__gxdpLXisBNaO2A" in
request methodPost "/rpc/get_current_user" [auth] request methodPost "/rpc/get_current_user" [auth]
[json| {} |] [json| {} |]
`shouldRespondWith` [str|"postgrest_test_default_role"|] `shouldRespondWith` [json|"postgrest_test_default_role"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -166,7 +165,7 @@ spec actualPgVersion = describe "authorization" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6M30.OGxEJAf60NKZiTn-tIb2jy4rqKs_ZruLGWZ40TjrJsM" in let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6M30.OGxEJAf60NKZiTn-tIb2jy4rqKs_ZruLGWZ40TjrJsM" in
request methodPost "/rpc/get_current_user" [auth] request methodPost "/rpc/get_current_user" [auth]
[json| {} |] [json| {} |]
`shouldRespondWith` [str|{"hint":"Please contact administrator","details":null,"code":"P0001","message":"Disabled ID --> 3"}|] `shouldRespondWith` [json|{"hint":"Please contact administrator","details":null,"code":"P0001","message":"Disabled ID --> 3"}|]
{ matchStatus = 400 { matchStatus = 400
, matchHeaders = [] , matchHeaders = []
} }
+5 -6
View File
@@ -6,7 +6,6 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import Protolude hiding (get) import Protolude hiding (get)
@@ -23,7 +22,7 @@ spec =
it "returns the deleted item and count if requested" $ it "returns the deleted item and count if requested" $
request methodDelete "/items?id=eq.2" [("Prefer", "return=representation"), ("Prefer", "count=exact")] "" request methodDelete "/items?id=eq.2" [("Prefer", "return=representation"), ("Prefer", "count=exact")] ""
`shouldRespondWith` [str|[{"id":2}]|] `shouldRespondWith` [json|[{"id":2}]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/1"] , matchHeaders = ["Content-Range" <:> "*/1"]
} }
@@ -42,18 +41,18 @@ spec =
it "returns the deleted item and shapes the response" $ it "returns the deleted item and shapes the response" $
request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] "" request methodDelete "/complex_items?id=eq.2&select=id,name" [("Prefer", "return=representation")] ""
`shouldRespondWith` [str|[{"id":2,"name":"Two"}]|] `shouldRespondWith` [json|[{"id":2,"name":"Two"}]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/*"] , matchHeaders = ["Content-Range" <:> "*/*"]
} }
it "can rename and cast the selected columns" $ it "can rename and cast the selected columns" $
request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] "" request methodDelete "/complex_items?id=eq.3&select=ciId:id::text,ciName:name" [("Prefer", "return=representation")] ""
`shouldRespondWith` [str|[{"ciId":"3","ciName":"Three"}]|] `shouldRespondWith` [json|[{"ciId":"3","ciName":"Three"}]|]
it "can embed (parent) entities" $ it "can embed (parent) entities" $
request methodDelete "/tasks?id=eq.8&select=id,name,project:projects(id)" [("Prefer", "return=representation")] "" request methodDelete "/tasks?id=eq.8&select=id,name,project:projects(id)" [("Prefer", "return=representation")] ""
`shouldRespondWith` [str|[{"id":8,"name":"Code OSX","project":{"id":4}}]|] `shouldRespondWith` [json|[{"id":8,"name":"Code OSX","project":{"id":4}}]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/*"] , matchHeaders = ["Content-Range" <:> "*/*"]
} }
@@ -61,7 +60,7 @@ spec =
it "actually clears items ouf the db" $ do it "actually clears items ouf the db" $ do
_ <- request methodDelete "/items?id=lt.15" [] "" _ <- request methodDelete "/items?id=lt.15" [] ""
get "/items" get "/items"
`shouldRespondWith` [str|[{"id":15}]|] `shouldRespondWith` [json|[{"id":15}]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = ["Content-Range" <:> "0-0/*"] , matchHeaders = ["Content-Range" <:> "0-0/*"]
} }
+2 -3
View File
@@ -5,7 +5,6 @@ import Network.Wai (Application)
import Test.Hspec hiding (pendingWith) import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -154,7 +153,7 @@ spec =
it "can embed parent with view!fk and grandparent by using fk" $ it "can embed parent with view!fk and grandparent by using fk" $
get "/tasks?id=eq.1&select=id,name,projects_view!project(id,name,client(id,name))" `shouldRespondWith` get "/tasks?id=eq.1&select=id,name,projects_view!project(id,name,client(id,name))" `shouldRespondWith`
[str|[{"id":1,"name":"Design w7","projects_view":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|] [json|[{"id":1,"name":"Design w7","projects_view":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|]
it "can embed by using a composite FK name" $ it "can embed by using a composite FK name" $
get "/unit_workdays?select=unit_id,day,fst_shift(car_id,schedule(name)),snd_shift(camera_id,schedule(name))" `shouldRespondWith` get "/unit_workdays?select=unit_id,day,fst_shift(car_id,schedule(name)),snd_shift(camera_id,schedule(name))" `shouldRespondWith`
@@ -230,7 +229,7 @@ spec =
it "can embed parent by using view!column and grandparent by using the column" $ it "can embed parent by using view!column and grandparent by using the column" $
get "/tasks?id=eq.1&select=id,name,project:projects_view!project_id(id,name,client:client_id(id,name))" `shouldRespondWith` get "/tasks?id=eq.1&select=id,name,project:projects_view!project_id(id,name,client:client_id(id,name))" `shouldRespondWith`
[str|[{"id":1,"name":"Design w7","project":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|] [json|[{"id":1,"name":"Design w7","project":{"id":1,"name":"Windows 7","client":{"id":1,"name":"Microsoft"}}}]|]
it "can specify table!column" $ it "can specify table!column" $
get "/message?select=id,body,sender:person!sender(name),recipient:person!recipient(name)&id=lt.4" `shouldRespondWith` get "/message?select=id,body,sender:person!sender(name),recipient:person!recipient(name)&id=lt.4" `shouldRespondWith`
+29 -30
View File
@@ -42,7 +42,7 @@ spec actualPgVersion = do
"integer": 14, "double": 3.14159, "varchar": "testing!" "integer": 14, "double": 3.14159, "varchar": "testing!"
, "boolean": false, "date": "1900-01-01", "money": "$3.99" , "boolean": false, "date": "1900-01-01", "money": "$3.99"
, "enum": "foo" , "enum": "foo"
}] |] `shouldRespondWith` [str|[{"integer":14,"varchar":"testing!"}]|] }] |] `shouldRespondWith` [json|[{"integer":14,"varchar":"testing!"}]|]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [matchContentTypeJson] , matchHeaders = [matchContentTypeJson]
} }
@@ -86,7 +86,7 @@ spec actualPgVersion = do
it "includes related data after insert" $ it "includes related data after insert" $
request methodPost "/projects?select=id,name,clients(id,name)" request methodPost "/projects?select=id,name,clients(id,name)"
[("Prefer", "return=representation"), ("Prefer", "count=exact")] [("Prefer", "return=representation"), ("Prefer", "count=exact")]
[str|{"id":6,"name":"New Project","client_id":2}|] `shouldRespondWith` [str|[{"id":6,"name":"New Project","clients":{"id":2,"name":"Apple"}}]|] [json|{"id":6,"name":"New Project","client_id":2}|] `shouldRespondWith` [json|[{"id":6,"name":"New Project","clients":{"id":2,"name":"Apple"}}]|]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [ matchContentTypeJson , matchHeaders = [ matchContentTypeJson
, "Location" <:> "/projects?id=eq.6" , "Location" <:> "/projects?id=eq.6"
@@ -96,8 +96,8 @@ spec actualPgVersion = do
it "can rename and cast the selected columns" $ it "can rename and cast the selected columns" $
request methodPost "/projects?select=pId:id::text,pName:name,cId:client_id::text" request methodPost "/projects?select=pId:id::text,pName:name,cId:client_id::text"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
[str|{"id":7,"name":"New Project","client_id":2}|] `shouldRespondWith` [json|{"id":7,"name":"New Project","client_id":2}|] `shouldRespondWith`
[str|[{"pId":"7","pName":"New Project","cId":"2"}]|] [json|[{"pId":"7","pName":"New Project","cId":"2"}]|]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [ matchContentTypeJson , matchHeaders = [ matchContentTypeJson
, "Location" <:> "/projects?id=eq.7" , "Location" <:> "/projects?id=eq.7"
@@ -106,8 +106,8 @@ spec actualPgVersion = do
it "should not throw and return location header when selecting without PK" $ it "should not throw and return location header when selecting without PK" $
request methodPost "/projects?select=name,client_id" [("Prefer", "return=representation")] request methodPost "/projects?select=name,client_id" [("Prefer", "return=representation")]
[str|{"id":10,"name":"New Project","client_id":2}|] `shouldRespondWith` [json|{"id":10,"name":"New Project","client_id":2}|] `shouldRespondWith`
[str|[{"name":"New Project","client_id":2}]|] [json|[{"name":"New Project","client_id":2}]|]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [ matchContentTypeJson , matchHeaders = [ matchContentTypeJson
, "Location" <:> "/projects?id=eq.10" , "Location" <:> "/projects?id=eq.10"
@@ -117,7 +117,7 @@ spec actualPgVersion = do
context "requesting no representation" $ context "requesting no representation" $
it "should not throw and return location header when selecting without PK" $ it "should not throw and return location header when selecting without PK" $
request methodPost "/projects?select=name,client_id" [] request methodPost "/projects?select=name,client_id" []
[str|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` "" [json|{"id":11,"name":"New Project","client_id":2}|] `shouldRespondWith` ""
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [ "Location" <:> "/projects?id=eq.11" , matchHeaders = [ "Location" <:> "/projects?id=eq.11"
, "Content-Range" <:> "*/*" ] , "Content-Range" <:> "*/*" ]
@@ -263,7 +263,7 @@ spec actualPgVersion = do
request methodPost "/json_table" request methodPost "/json_table"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
inserted inserted
`shouldRespondWith` [str|[{"data":{"foo":"bar"}}]|] `shouldRespondWith` [json|[{"data":{"foo":"bar"}}]|]
{ matchStatus = 201 { matchStatus = 201
} }
@@ -272,7 +272,7 @@ spec actualPgVersion = do
request methodPost "/json_table" request methodPost "/json_table"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
inserted inserted
`shouldRespondWith` [str|[{"data":[1,2,3]}]|] `shouldRespondWith` [json|[{"data":[1,2,3]}]|]
{ matchStatus = 201 { matchStatus = 201
} }
@@ -423,24 +423,23 @@ spec actualPgVersion = do
describe "Row level permission" $ describe "Row level permission" $
it "set user_id when inserting rows" $ do it "set user_id when inserting rows" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0" post "/postgrest/users" [json| { "id":"jdoe", "pass": "1234", "role": "postgrest_test_author" } |]
_ <- post "/postgrest/users" [json| { "id":"jdoe", "pass": "1234", "role": "postgrest_test_author" } |] post "/postgrest/users" [json| { "id":"jroe", "pass": "1234", "role": "postgrest_test_author" } |]
_ <- post "/postgrest/users" [json| { "id":"jroe", "pass": "1234", "role": "postgrest_test_author" } |]
p1 <- request methodPost "/authors_only" request methodPost "/authors_only"
[ auth, ("Prefer", "return=representation") ] [ authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0", ("Prefer", "return=representation") ]
[json| { "secret": "nyancat" } |] [json| { "secret": "nyancat" } |]
liftIO $ do `shouldRespondWith`
simpleBody p1 `shouldBe` [str|[{"owner":"jdoe","secret":"nyancat"}]|] [json|[{"owner":"jdoe","secret":"nyancat"}]|]
simpleStatus p1 `shouldBe` created201 { matchStatus = 201 }
p2 <- request methodPost "/authors_only" request methodPost "/authors_only"
-- jwt token for jroe -- jwt token for jroe
[ authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqcm9lIn0.2e7mx0U4uDcInlbJVOBGlrRufwqWLINDIEDC1vS0nw8", ("Prefer", "return=representation") ] [ authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqcm9lIn0.2e7mx0U4uDcInlbJVOBGlrRufwqWLINDIEDC1vS0nw8", ("Prefer", "return=representation") ]
[json| { "secret": "lolcat", "owner": "hacker" } |] [json| { "secret": "lolcat", "owner": "hacker" } |]
liftIO $ do `shouldRespondWith`
simpleBody p2 `shouldBe` [str|[{"owner":"jroe","secret":"lolcat"}]|] [json|[{"owner":"jroe","secret":"lolcat"}]|]
simpleStatus p2 `shouldBe` created201 { matchStatus = 201 }
context "tables with self reference foreign keys" $ do context "tables with self reference foreign keys" $ do
it "embeds parent after insert" $ it "embeds parent after insert" $
@@ -456,7 +455,7 @@ spec actualPgVersion = do
context "table with limited privileges" $ do context "table with limited privileges" $ do
it "succeeds inserting if correct select is applied" $ it "succeeds inserting if correct select is applied" $
request methodPost "/limited_article_stars?select=article_id,user_id" [("Prefer", "return=representation")] request methodPost "/limited_article_stars?select=article_id,user_id" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 1} |] `shouldRespondWith` [str|[{"article_id":2,"user_id":1}]|] [json| {"article_id": 2, "user_id": 1} |] `shouldRespondWith` [json|[{"article_id":2,"user_id":1}]|]
{ matchStatus = 201 { matchStatus = 201
, matchHeaders = [] , matchHeaders = []
} }
@@ -465,9 +464,9 @@ spec actualPgVersion = do
request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")] request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith` ( [json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|] [json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|] [json|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
) )
{ matchStatus = 401 { matchStatus = 401
, matchHeaders = [] , matchHeaders = []
@@ -477,9 +476,9 @@ spec actualPgVersion = do
request methodPost "/limited_article_stars" [("Prefer", "return=representation")] request methodPost "/limited_article_stars" [("Prefer", "return=representation")]
[json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith` ( [json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|] [json|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|] [json|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
) )
{ matchStatus = 401 { matchStatus = 401
, matchHeaders = [] , matchHeaders = []
+10 -12
View File
@@ -8,8 +8,6 @@ import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import PostgREST.Types (PgVersion, pgVersion112, pgVersion121, import PostgREST.Types (PgVersion, pgVersion112, pgVersion121,
pgVersion96) pgVersion96)
import Protolude hiding (get) import Protolude hiding (get)
@@ -72,19 +70,19 @@ spec actualPgVersion = do
[json| [{"a": null, "b": null}] |] [json| [{"a": null, "b": null}] |]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
get "/nullable_integer?a=is.null" `shouldRespondWith` [str|[{"a":null}]|] get "/nullable_integer?a=is.null" `shouldRespondWith` [json|[{"a":null}]|]
it "matches with like" $ do it "matches with like" $ do
get "/simple_pk?k=like.*yx" `shouldRespondWith` get "/simple_pk?k=like.*yx" `shouldRespondWith`
[str|[{"k":"xyyx","extra":"u"}]|] [json|[{"k":"xyyx","extra":"u"}]|]
get "/simple_pk?k=like.xy*" `shouldRespondWith` get "/simple_pk?k=like.xy*" `shouldRespondWith`
[str|[{"k":"xyyx","extra":"u"}]|] [json|[{"k":"xyyx","extra":"u"}]|]
get "/simple_pk?k=like.*YY*" `shouldRespondWith` get "/simple_pk?k=like.*YY*" `shouldRespondWith`
[str|[{"k":"xYYx","extra":"v"}]|] [json|[{"k":"xYYx","extra":"v"}]|]
it "matches with like using not operator" $ it "matches with like using not operator" $
get "/simple_pk?k=not.like.*yx" `shouldRespondWith` get "/simple_pk?k=not.like.*yx" `shouldRespondWith`
[str|[{"k":"xYYx","extra":"v"}]|] [json|[{"k":"xYYx","extra":"v"}]|]
it "matches with ilike" $ do it "matches with ilike" $ do
get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith` get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith`
@@ -251,11 +249,11 @@ spec actualPgVersion = do
describe "Shaping response with select parameter" $ do describe "Shaping response with select parameter" $ do
it "selectStar works in absense of parameter" $ it "selectStar works in absense of parameter" $
get "/complex_items?id=eq.3" `shouldRespondWith` get "/complex_items?id=eq.3" `shouldRespondWith`
[str|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}]|] [json|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3],"field-with_sep":1}]|]
it "dash `-` in column names is accepted" $ it "dash `-` in column names is accepted" $
get "/complex_items?id=eq.3&select=id,field-with_sep" `shouldRespondWith` get "/complex_items?id=eq.3&select=id,field-with_sep" `shouldRespondWith`
[str|[{"id":3,"field-with_sep":1}]|] [json|[{"id":3,"field-with_sep":1}]|]
it "one simple column" $ it "one simple column" $
get "/complex_items?select=id" `shouldRespondWith` get "/complex_items?select=id" `shouldRespondWith`
@@ -320,7 +318,7 @@ spec actualPgVersion = do
it "requesting parents and filtering parent columns" $ it "requesting parents and filtering parent columns" $
get "/projects?id=eq.1&select=id, name, clients(id)" `shouldRespondWith` get "/projects?id=eq.1&select=id, name, clients(id)" `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|] [json|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|]
it "rows with missing parents are included" $ it "rows with missing parents are included" $
get "/projects?id=in.(1,5)&select=id,clients(id)" `shouldRespondWith` get "/projects?id=in.(1,5)&select=id,clients(id)" `shouldRespondWith`
@@ -329,7 +327,7 @@ spec actualPgVersion = do
it "rows with no children return [] instead of null" $ it "rows with no children return [] instead of null" $
get "/projects?id=in.(5)&select=id,tasks(id)" `shouldRespondWith` get "/projects?id=in.(5)&select=id,tasks(id)" `shouldRespondWith`
[str|[{"id":5,"tasks":[]}]|] [json|[{"id":5,"tasks":[]}]|]
it "requesting children 2 levels" $ it "requesting children 2 levels" $
get "/clients?id=eq.1&select=id,projects(id,tasks(id))" `shouldRespondWith` get "/clients?id=eq.1&select=id,projects(id,tasks(id))" `shouldRespondWith`
@@ -660,7 +658,7 @@ spec actualPgVersion = do
it "ordering embeded parents does not break things" $ it "ordering embeded parents does not break things" $
get "/projects?id=eq.1&select=id, name, clients(id, name)&clients.order=name.asc" `shouldRespondWith` get "/projects?id=eq.1&select=id, name, clients(id, name)&clients.order=name.asc" `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"}}]|] [json|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"}}]|]
context "order syntax errors" $ do context "order syntax errors" $ do
it "gives meaningful error messages when asc/desc/nulls{first,last} are misspelled" $ do it "gives meaningful error messages when asc/desc/nulls{first,last} are misspelled" $ do
+14 -14
View File
@@ -123,9 +123,9 @@ spec actualPgVersion =
context "shaping the response returned by a proc" $ do context "shaping the response returned by a proc" $ do
it "returns a project" $ do it "returns a project" $ do
post "/rpc/getproject" [json| { "id": 1} |] `shouldRespondWith` post "/rpc/getproject" [json| { "id": 1} |] `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7","client_id":1}]|] [json|[{"id":1,"name":"Windows 7","client_id":1}]|]
get "/rpc/getproject?id=1" `shouldRespondWith` get "/rpc/getproject?id=1" `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7","client_id":1}]|] [json|[{"id":1,"name":"Windows 7","client_id":1}]|]
it "can filter proc results" $ do it "can filter proc results" $ do
post "/rpc/getallprojects?id=gt.1&id=lt.5&select=id" [json| {} |] `shouldRespondWith` post "/rpc/getallprojects?id=gt.1&id=lt.5&select=id" [json| {} |] `shouldRespondWith`
@@ -147,9 +147,9 @@ spec actualPgVersion =
it "select works on the first level" $ do it "select works on the first level" $ do
post "/rpc/getproject?select=id,name" [json| { "id": 1} |] `shouldRespondWith` post "/rpc/getproject?select=id,name" [json| { "id": 1} |] `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7"}]|] [json|[{"id":1,"name":"Windows 7"}]|]
get "/rpc/getproject?id=1&select=id,name" `shouldRespondWith` get "/rpc/getproject?id=1&select=id,name" `shouldRespondWith`
[str|[{"id":1,"name":"Windows 7"}]|] [json|[{"id":1,"name":"Windows 7"}]|]
context "foreign entities embedding" $ do context "foreign entities embedding" $ do
it "can embed if related tables are in the exposed schema" $ do it "can embed if related tables are in the exposed schema" $ do
@@ -624,7 +624,7 @@ spec actualPgVersion =
get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|] get "/rpc/overloaded?a=1&b=2" `shouldRespondWith` [str|3|]
it "overloaded(text, text, text)" $ it "overloaded(text, text, text)" $
get "/rpc/overloaded?a=1&b=2&c=3" `shouldRespondWith` [str|"123"|] get "/rpc/overloaded?a=1&b=2&c=3" `shouldRespondWith` [json|"123"|]
context "only for POST rpc" $ do context "only for POST rpc" $ do
it "gives a parse filter error if GET style proc args are specified" $ it "gives a parse filter error if GET style proc args are specified" $
@@ -688,7 +688,7 @@ spec actualPgVersion =
[("Custom-Header", "test")] [("Custom-Header", "test")]
[json| { "name": "request.header.custom-header" } |] [json| { "name": "request.header.custom-header" } |]
`shouldRespondWith` `shouldRespondWith`
[str|"test"|] [json|"test"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [ matchContentTypeJson ] , matchHeaders = [ matchContentTypeJson ]
} }
@@ -697,7 +697,7 @@ spec actualPgVersion =
[("Origin", "http://example.com")] [("Origin", "http://example.com")]
[json| { "name": "request.header.origin" } |] [json| { "name": "request.header.origin" } |]
`shouldRespondWith` `shouldRespondWith`
[str|"http://example.com"|] [json|"http://example.com"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [ matchContentTypeJson ] , matchHeaders = [ matchContentTypeJson ]
} }
@@ -705,7 +705,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [] request methodPost "/rpc/get_guc_value" []
[json| { "name": "request.jwt.claim.role" } |] [json| { "name": "request.jwt.claim.role" } |]
`shouldRespondWith` `shouldRespondWith`
[str|"postgrest_test_anonymous"|] [json|"postgrest_test_anonymous"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [ matchContentTypeJson ] , matchHeaders = [ matchContentTypeJson ]
} }
@@ -713,7 +713,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue")] request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue")]
[json| {"name":"request.cookie.acookie"} |] [json| {"name":"request.cookie.acookie"} |]
`shouldRespondWith` `shouldRespondWith`
[str|"cookievalue"|] [json|"cookievalue"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -721,7 +721,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue;secondcookie=anothervalue")] request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue;secondcookie=anothervalue")]
[json| {"name":"request.cookie.secondcookie"} |] [json| {"name":"request.cookie.secondcookie"} |]
`shouldRespondWith` `shouldRespondWith`
[str|"anothervalue"|] [json|"anothervalue"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -729,7 +729,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [] request methodPost "/rpc/get_guc_value" []
[json| { "name": "app.settings.app_host" } |] [json| { "name": "app.settings.app_host" } |]
`shouldRespondWith` `shouldRespondWith`
[str|"localhost"|] [json|"localhost"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [ matchContentTypeJson ] , matchHeaders = [ matchContentTypeJson ]
} }
@@ -737,7 +737,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"] request methodPost "/rpc/get_guc_value" [authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"]
[json| {"name":"request.header.authorization"} |] [json| {"name":"request.header.authorization"} |]
`shouldRespondWith` `shouldRespondWith`
[str|"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"|] [json|"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -745,7 +745,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [] request methodPost "/rpc/get_guc_value" []
[json| {"name":"request.method"} |] [json| {"name":"request.method"} |]
`shouldRespondWith` `shouldRespondWith`
[str|"POST"|] [json|"POST"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -753,7 +753,7 @@ spec actualPgVersion =
request methodPost "/rpc/get_guc_value" [] request methodPost "/rpc/get_guc_value" []
[json| {"name":"request.path"} |] [json| {"name":"request.path"} |]
`shouldRespondWith` `shouldRespondWith`
[str|"/rpc/get_guc_value"|] [json|"/rpc/get_guc_value"|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
+55 -45
View File
@@ -7,7 +7,6 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import Protolude hiding (get) import Protolude hiding (get)
import SpecHelper import SpecHelper
@@ -26,31 +25,37 @@ spec =
it "will select an existing object" $ do it "will select an existing object" $ do
request methodGet "/items?id=eq.5" [singular] "" request methodGet "/items?id=eq.5" [singular] ""
`shouldRespondWith` [str|{"id":5}|] `shouldRespondWith`
[json|{"id":5}|]
{ matchHeaders = [matchContentTypeSingular] }
-- also test without the +json suffix -- also test without the +json suffix
request methodGet "/items?id=eq.5" request methodGet "/items?id=eq.5"
[("Accept", "application/vnd.pgrst.object")] "" [("Accept", "application/vnd.pgrst.object")] ""
`shouldRespondWith` [str|{"id":5}|] `shouldRespondWith`
[json|{"id":5}|]
{ matchHeaders = [matchContentTypeSingular] }
it "can combine multiple prefer values" $ it "can combine multiple prefer values" $
request methodGet "/items?id=eq.5" [singular, ("Prefer","count=none")] "" request methodGet "/items?id=eq.5" [singular, ("Prefer","count=none")] ""
`shouldRespondWith` [str|{"id":5}|] `shouldRespondWith`
[json|{"id":5}|]
{ matchHeaders = [matchContentTypeSingular] }
it "can shape plurality singular object routes" $ it "can shape plurality singular object routes" $
request methodGet "/projects_view?id=eq.1&select=id,name,clients(*),tasks(id,name)" [singular] "" request methodGet "/projects_view?id=eq.1&select=id,name,clients(*),tasks(id,name)" [singular] ""
`shouldRespondWith` `shouldRespondWith`
[json|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|] [json|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|]
{ matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] } { matchHeaders = [matchContentTypeSingular] }
context "when updating rows" $ do context "when updating rows" $ do
it "works for one row with return=rep" $ do it "works for one row with return=rep" $ do
_ <- post "/addresses" [json| { id: 97, address: "A Street" } |] post "/addresses" [json| { id: 97, address: "A Street" } |]
request methodPatch request methodPatch "/addresses?id=eq.97"
"/addresses?id=eq.97" [("Prefer", "return=representation"), singular]
[("Prefer", "return=representation"), singular] [json| { address: "B Street" } |]
[json| { address: "B Street" } |]
`shouldRespondWith` `shouldRespondWith`
[str|{"id":97,"address":"B Street"}|] [json|{"id":97,"address":"B Street"}|]
{ matchHeaders = [matchContentTypeSingular] }
it "works for one row with return=minimal" $ it "works for one row with return=minimal" $
request methodPatch request methodPatch
@@ -72,7 +77,7 @@ spec =
isErrorFormat (simpleBody p) `shouldBe` True isErrorFormat (simpleBody p) `shouldBe` True
-- the rows should not be updated, either -- the rows should not be updated, either
get "/addresses?id=eq.98" `shouldRespondWith` [str|[{"id":98,"address":"xxx"}]|] get "/addresses?id=eq.98" `shouldRespondWith` [json|[{"id":98,"address":"xxx"}]|]
it "raises an error for multiple rows with return=rep" $ do it "raises an error for multiple rows with return=rep" $ do
_ <- post "/addresses" [json| { id: 100, address: "xxx" } |] _ <- post "/addresses" [json| { id: 100, address: "xxx" } |]
@@ -85,33 +90,36 @@ spec =
isErrorFormat (simpleBody p) `shouldBe` True isErrorFormat (simpleBody p) `shouldBe` True
-- the rows should not be updated, either -- the rows should not be updated, either
get "/addresses?id=eq.100" `shouldRespondWith` [str|[{"id":100,"address":"xxx"}]|] get "/addresses?id=eq.100" `shouldRespondWith` [json|[{"id":100,"address":"xxx"}]|]
it "raises an error for zero rows" $ it "raises an error for zero rows" $
request methodPatch "/items?id=gt.0&id=lt.0" request methodPatch "/items?id=gt.0&id=lt.0"
[singular] [json|{"id":1}|] [singular] [json|{"id":1}|]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
it "raises an error for zero rows with return=rep" $ it "raises an error for zero rows with return=rep" $
request methodPatch "/items?id=gt.0&id=lt.0" request methodPatch "/items?id=gt.0&id=lt.0"
[("Prefer", "return=representation"), singular] [json|{"id":1}|] [("Prefer", "return=representation"), singular] [json|{"id":1}|]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
context "when creating rows" $ do context "when creating rows" $ do
it "works for one row with return=rep" $ do it "works for one row with return=rep" $ do
p <- request methodPost request methodPost "/addresses"
"/addresses" [("Prefer", "return=representation"), singular]
[("Prefer", "return=representation"), singular] [json| [ { id: 102, address: "xxx" } ] |]
[json| [ { id: 102, address: "xxx" } ] |] `shouldRespondWith`
liftIO $ simpleBody p `shouldBe` [str|{"id":102,"address":"xxx"}|] [json|{"id":102,"address":"xxx"}|]
{ matchStatus = 201
, matchHeaders = [matchContentTypeSingular]
}
it "works for one row with return=minimal" $ do it "works for one row with return=minimal" $ do
request methodPost "/addresses" request methodPost "/addresses"
@@ -123,7 +131,7 @@ spec =
} }
-- and the element should exist -- and the element should exist
get "/addresses?id=eq.103" get "/addresses?id=eq.103"
`shouldRespondWith` [str|[{"id":103,"address":"xxx"}]|] `shouldRespondWith` [json|[{"id":103,"address":"xxx"}]|]
{ matchStatus = 200 { matchStatus = 200
, matchHeaders = [] , matchHeaders = []
} }
@@ -153,9 +161,9 @@ spec =
[("Prefer", "return=minimal"), singular] [("Prefer", "return=minimal"), singular]
[json| [ { id: 204, address: "xxx" }, { id: 205, address: "yyy" } ] |] [json| [ { id: 204, address: "xxx" }, { id: 205, address: "yyy" } ] |]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [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 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
-- the rows should not exist, either -- the rows should not exist, either
@@ -166,9 +174,9 @@ spec =
[singular] [singular]
[json| [ ] |] [json| [ ] |]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
it "raises an error when creating zero entities with return=rep" $ it "raises an error when creating zero entities with return=rep" $
@@ -176,9 +184,9 @@ spec =
[("Prefer", "return=representation"), singular] [("Prefer", "return=representation"), singular]
[json| [ ] |] [json| [ ] |]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
context "when deleting rows" $ do context "when deleting rows" $ do
@@ -186,7 +194,7 @@ spec =
p <- request methodDelete p <- request methodDelete
"/items?id=eq.11" "/items?id=eq.11"
[("Prefer", "return=representation"), singular] "" [("Prefer", "return=representation"), singular] ""
liftIO $ simpleBody p `shouldBe` [str|{"id":11}|] liftIO $ simpleBody p `shouldBe` [json|{"id":11}|]
it "works for one row with return=minimal" $ do it "works for one row with return=minimal" $ do
p <- request methodDelete p <- request methodDelete
@@ -222,18 +230,18 @@ spec =
request methodDelete "/items?id=lt.0" request methodDelete "/items?id=lt.0"
[singular] "" [singular] ""
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
it "raises an error when deleting zero entities with return=rep" $ it "raises an error when deleting zero entities with return=rep" $
request methodDelete "/items?id=lt.0" request methodDelete "/items?id=lt.0"
[("Prefer", "return=representation"), singular] "" [("Prefer", "return=representation"), singular] ""
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
context "when calling a stored proc" $ do context "when calling a stored proc" $ do
@@ -241,9 +249,9 @@ spec =
request methodPost "/rpc/getproject" request methodPost "/rpc/getproject"
[singular] [json|{ "id": 9999999}|] [singular] [json|{ "id": 9999999}|]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [json|{"details":"Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|]
{ matchStatus = 406 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
-- this one may be controversial, should vnd.pgrst.object include -- this one may be controversial, should vnd.pgrst.object include
@@ -255,30 +263,32 @@ spec =
it "returns a single object for json proc" $ it "returns a single object for json proc" $
request methodPost "/rpc/getproject" request methodPost "/rpc/getproject"
[singular] [json|{ "id": 1}|] `shouldRespondWith` [singular] [json|{ "id": 1}|]
[str|{"id":1,"name":"Windows 7","client_id":1}|] `shouldRespondWith`
[json|{"id":1,"name":"Windows 7","client_id":1}|]
{ matchHeaders = [matchContentTypeSingular] }
it "fails for multiple rows" $ it "fails for multiple rows" $
request methodPost "/rpc/getallprojects" request methodPost "/rpc/getallprojects"
[singular] "{}" [singular] "{}"
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 5 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [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 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
it "executes the proc exactly once per request" $ do it "executes the proc exactly once per request" $ do
request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |] request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |]
`shouldRespondWith` [str|[{"id":1,"name":"Windows 7"}]|] `shouldRespondWith` [json|[{"id":1,"name":"Windows 7"}]|]
request methodPost "/rpc/setprojects" [singular] request methodPost "/rpc/setprojects" [singular]
[json| {"id_l": 1, "id_h": 2, "name": "changed"} |] [json| {"id_l": 1, "id_h": 2, "name": "changed"} |]
`shouldRespondWith` `shouldRespondWith`
[str|{"details":"Results contain 2 rows, application/vnd.pgrst.object+json requires 1 row","message":"JSON object requested, multiple (or no) rows returned"}|] [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 { matchStatus = 406
, matchHeaders = ["Content-Type" <:> "application/vnd.pgrst.object+json; charset=utf-8"] , matchHeaders = [matchContentTypeSingular]
} }
-- should not actually have executed the function -- should not actually have executed the function
request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |] request methodPost "/rpc/getproject?select=id,name" [] [json| {"id": 1} |]
`shouldRespondWith` [str|[{"id":1,"name":"Windows 7"}]|] `shouldRespondWith` [json|[{"id":1,"name":"Windows 7"}]|]
+1 -1
View File
@@ -85,7 +85,7 @@ spec = do
it "returns updated object as array when return=rep" $ it "returns updated object as array when return=rep" $
request methodPatch "/items?id=eq.2" request methodPatch "/items?id=eq.2"
[("Prefer", "return=representation")] [json| { "id":2 } |] [("Prefer", "return=representation")] [json| { "id":2 } |]
`shouldRespondWith` [str|[{"id":2}]|] `shouldRespondWith` [json|[{"id":2}]|]
{ matchStatus = 200, { matchStatus = 200,
matchHeaders = ["Content-Range" <:> "0-0/*"] matchHeaders = ["Content-Range" <:> "0-0/*"]
} }
+21 -22
View File
@@ -6,7 +6,6 @@ import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON import Test.Hspec.Wai.JSON
import Text.Heredoc
import Protolude hiding (get, put) import Protolude hiding (get, put)
import SpecHelper import SpecHelper
@@ -161,73 +160,73 @@ spec =
context "Restrictions" $ do context "Restrictions" $ do
it "fails if Range is specified" $ it "fails if Range is specified" $
request methodPut "/tiobe_pls?name=eq.Javascript" [("Range", "0-5")] request methodPut "/tiobe_pls?name=eq.Javascript" [("Range", "0-5")]
[str| [ { "name": "Javascript", "rank": 1 } ]|] [json| [ { "name": "Javascript", "rank": 1 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|] [json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] } { matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
it "fails if limit is specified" $ it "fails if limit is specified" $
put "/tiobe_pls?name=eq.Javascript&limit=1" put "/tiobe_pls?name=eq.Javascript&limit=1"
[str| [ { "name": "Javascript", "rank": 1 } ]|] [json| [ { "name": "Javascript", "rank": 1 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|] [json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] } { matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
it "fails if offset is specified" $ it "fails if offset is specified" $
put "/tiobe_pls?name=eq.Javascript&offset=1" put "/tiobe_pls?name=eq.Javascript&offset=1"
[str| [ { "name": "Javascript", "rank": 1 } ]|] [json| [ { "name": "Javascript", "rank": 1 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|] [json|{"message":"Range header and limit/offset querystring parameters are not allowed for PUT"}|]
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] } { matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
it "rejects every other filter than pk cols eq's" $ do it "rejects every other filter than pk cols eq's" $ do
put "/tiobe_pls?rank=eq.19" put "/tiobe_pls?rank=eq.19"
[str| [ { "name": "Go", "rank": 19 } ]|] [json| [ { "name": "Go", "rank": 19 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
put "/tiobe_pls?id=not.eq.Java" put "/tiobe_pls?id=not.eq.Java"
[str| [ { "name": "Go", "rank": 19 } ]|] [json| [ { "name": "Go", "rank": 19 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
put "/tiobe_pls?id=in.(Go)" put "/tiobe_pls?id=in.(Go)"
[str| [ { "name": "Go", "rank": 19 } ]|] [json| [ { "name": "Go", "rank": 19 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
put "/tiobe_pls?and=(id.eq.Go)" put "/tiobe_pls?and=(id.eq.Go)"
[str| [ { "name": "Go", "rank": 19 } ]|] [json| [ { "name": "Go", "rank": 19 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
it "fails if not all composite key cols are specified as eq filters" $ do it "fails if not all composite key cols are specified as eq filters" $ do
put "/employees?first_name=eq.Susan" put "/employees?first_name=eq.Susan"
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
put "/employees?last_name=eq.Heidt" put "/employees?last_name=eq.Heidt"
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
it "fails if the uri primary key doesn't match the payload primary key" $ do it "fails if the uri primary key doesn't match the payload primary key" $ do
put "/tiobe_pls?name=eq.MATLAB" [str| [ { "name": "Perl", "rank": 17 } ]|] put "/tiobe_pls?name=eq.MATLAB" [json| [ { "name": "Perl", "rank": 17 } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Payload values do not match URL in primary key column(s)"}|] [json|{"message":"Payload values do not match URL in primary key column(s)"}|]
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] } { matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
put "/employees?first_name=eq.Wendy&last_name=eq.Anderson" put "/employees?first_name=eq.Wendy&last_name=eq.Anderson"
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Payload values do not match URL in primary key column(s)"}|] [json|{"message":"Payload values do not match URL in primary key column(s)"}|]
{ matchStatus = 400 , matchHeaders = [matchContentTypeJson] } { matchStatus = 400 , matchHeaders = [matchContentTypeJson] }
it "fails if the table has no PK" $ it "fails if the table has no PK" $
put "/no_pk?a=eq.one&b=eq.two" [str| [ { "a": "one", "b": "two" } ]|] put "/no_pk?a=eq.one&b=eq.two" [json| [ { "a": "one", "b": "two" } ]|]
`shouldRespondWith` `shouldRespondWith`
[json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|] [json|{"message":"Filters must include all and only primary key columns with 'eq' operators"}|]
{ matchStatus = 405 , matchHeaders = [matchContentTypeJson] } { matchStatus = 405 , matchHeaders = [matchContentTypeJson] }
@@ -235,14 +234,14 @@ spec =
context "Inserting row" $ do context "Inserting row" $ do
it "succeeds on table with single pk col" $ do it "succeeds on table with single pk col" $ do
get "/tiobe_pls?name=eq.Go" `shouldRespondWith` "[]" get "/tiobe_pls?name=eq.Go" `shouldRespondWith` "[]"
put "/tiobe_pls?name=eq.Go" [str| [ { "name": "Go", "rank": 19 } ]|] `shouldRespondWith` 204 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] } get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json| [ { "name": "Go", "rank": 19 } ]|] { matchHeaders = [matchContentTypeJson] }
it "succeeds on table with composite pk" $ do it "succeeds on table with composite pk" $ do
get "/employees?first_name=eq.Susan&last_name=eq.Heidt" get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
`shouldRespondWith` "[]" `shouldRespondWith` "[]"
put "/employees?first_name=eq.Susan&last_name=eq.Heidt" put "/employees?first_name=eq.Susan&last_name=eq.Heidt"
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "48000", "company": "GEX", "occupation": "Railroad engineer" } ]|]
`shouldRespondWith` 204 `shouldRespondWith` 204
get "/employees?first_name=eq.Susan&last_name=eq.Heidt" get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
`shouldRespondWith` `shouldRespondWith`
@@ -251,19 +250,19 @@ spec =
it "succeeds if the table has only PK cols and no other cols" $ do it "succeeds if the table has only PK cols and no other cols" $ do
get "/only_pk?id=eq.10" `shouldRespondWith` "[]" get "/only_pk?id=eq.10" `shouldRespondWith` "[]"
put "/only_pk?id=eq.10" [str|[ { "id": 10 } ]|] `shouldRespondWith` 204 put "/only_pk?id=eq.10" [json|[ { "id": 10 } ]|] `shouldRespondWith` 204
get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] } get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] }
context "Updating row" $ do context "Updating row" $ do
it "succeeds on table with single pk col" $ do it "succeeds on table with single pk col" $ do
get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json|[ { "name": "Go", "rank": 19 } ]|] { matchHeaders = [matchContentTypeJson] } get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json|[ { "name": "Go", "rank": 19 } ]|] { matchHeaders = [matchContentTypeJson] }
put "/tiobe_pls?name=eq.Go" [str| [ { "name": "Go", "rank": 13 } ]|] `shouldRespondWith` 204 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] } get "/tiobe_pls?name=eq.Go" `shouldRespondWith` [json| [ { "name": "Go", "rank": 13 } ]|] { matchHeaders = [matchContentTypeJson] }
it "succeeds if the payload has more than one row, but it only puts the first element" $ it "succeeds if the payload has more than one row, but it only puts the first element" $
request methodPut "/tiobe_pls?name=eq.Go" request methodPut "/tiobe_pls?name=eq.Go"
[("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")] [("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")]
[str| [ { "name": "Go", "rank": 19 }, { "name": "Swift", "rank": 12 } ] |] [json| [ { "name": "Go", "rank": 19 }, { "name": "Swift", "rank": 12 } ] |]
`shouldRespondWith` `shouldRespondWith`
[json|{ "name": "Go", "rank": 19 }|] [json|{ "name": "Go", "rank": 19 }|]
{ matchStatus = 200 , matchHeaders = [matchContentTypeSingular] } { matchStatus = 200 , matchHeaders = [matchContentTypeSingular] }
@@ -274,7 +273,7 @@ spec =
[json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "$48,000.00", "company": "GEX", "occupation": "Railroad engineer" } ]|]
{ matchHeaders = [matchContentTypeJson] } { matchHeaders = [matchContentTypeJson] }
put "/employees?first_name=eq.Susan&last_name=eq.Heidt" put "/employees?first_name=eq.Susan&last_name=eq.Heidt"
[str| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "60000", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|] [json| [ { "first_name": "Susan", "last_name": "Heidt", "salary": "60000", "company": "Gamma Gas", "occupation": "Railroad engineer" } ]|]
`shouldRespondWith` 204 `shouldRespondWith` 204
get "/employees?first_name=eq.Susan&last_name=eq.Heidt" get "/employees?first_name=eq.Susan&last_name=eq.Heidt"
`shouldRespondWith` `shouldRespondWith`
@@ -283,13 +282,13 @@ spec =
it "succeeds if the table has only PK cols and no other cols" $ do 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] } get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] }
put "/only_pk?id=eq.10" [str|[ { "id": 10 } ]|] `shouldRespondWith` 204 put "/only_pk?id=eq.10" [json|[ { "id": 10 } ]|] `shouldRespondWith` 204
get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] } get "/only_pk?id=eq.10" `shouldRespondWith` [json|[ { "id": 10 } ]|] { matchHeaders = [matchContentTypeJson] }
it "works with return=representation and vnd.pgrst.object+json" $ it "works with return=representation and vnd.pgrst.object+json" $
request methodPut "/tiobe_pls?name=eq.Ruby" request methodPut "/tiobe_pls?name=eq.Ruby"
[("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")] [("Prefer", "return=representation"), ("Accept", "application/vnd.pgrst.object+json")]
[str| [ { "name": "Ruby", "rank": 11 } ]|] [json| [ { "name": "Ruby", "rank": 11 } ]|]
`shouldRespondWith` [json|{ "name": "Ruby", "rank": 11 }|] { matchHeaders = [matchContentTypeSingular] } `shouldRespondWith` [json|{ "name": "Ruby", "rank": 11 }|] { matchHeaders = [matchContentTypeSingular] }
context "with a camel case pk column" $ do context "with a camel case pk column" $ do
@@ -315,6 +314,6 @@ spec =
} }
it "works with PUT" $ do it "works with PUT" $ do
put "/UnitTest?idUnitTest=eq.1" [str| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] `shouldRespondWith` 204 put "/UnitTest?idUnitTest=eq.1" [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] `shouldRespondWith` 204
get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith` get "/UnitTest?idUnitTest=eq.1" `shouldRespondWith`
[json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] { matchHeaders = [matchContentTypeJson] } [json| [ { "idUnitTest": 1, "nameUnitTest": "unit test 1" } ]|] { matchHeaders = [matchContentTypeJson] }