Created
January 23, 2016 22:08
-
-
Save begriffs/c0e728ae297fffe8638e to your computer and use it in GitHub Desktop.
A stash I have laying around that tries to use a db pool for tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/test/Feature/AuthSpec.hs b/test/Feature/AuthSpec.hs | |
| index f678ae2..6f7d8bb 100644 | |
| --- a/test/Feature/AuthSpec.hs | |
| +++ b/test/Feature/AuthSpec.hs | |
| @@ -6,13 +6,14 @@ import Test.Hspec.Wai | |
| import Test.Hspec.Wai.JSON | |
| import Network.HTTP.Types | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| -- }}} | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| -spec struct c = around (withApp cfgDefault struct c) | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| +spec struct pool = around (withApp cfgDefault struct pool) | |
| $ describe "authorization" $ do | |
| it "hides tables that anonymous does not own" $ | |
| diff --git a/test/Feature/CorsSpec.hs b/test/Feature/CorsSpec.hs | |
| index 811af71..eea9993 100644 | |
| --- a/test/Feature/CorsSpec.hs | |
| +++ b/test/Feature/CorsSpec.hs | |
| @@ -6,6 +6,7 @@ import Test.Hspec.Wai | |
| import Network.Wai.Test (SResponse(simpleHeaders, simpleBody)) | |
| import qualified Data.ByteString.Lazy as BL | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| @@ -13,7 +14,7 @@ import PostgREST.Types (DbStructure(..)) | |
| import Network.HTTP.Types | |
| -- }}} | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = around (withApp cfgDefault struct c) $ describe "CORS" $ do | |
| let preflightHeaders = [ | |
| ("Accept", "*/*"), | |
| diff --git a/test/Feature/DeleteSpec.hs b/test/Feature/DeleteSpec.hs | |
| index ba9c60e..e2d4a62 100644 | |
| --- a/test/Feature/DeleteSpec.hs | |
| +++ b/test/Feature/DeleteSpec.hs | |
| @@ -7,10 +7,11 @@ import Text.Heredoc | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import Network.HTTP.Types | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = beforeAll resetDb | |
| . around (withApp cfgDefault struct c) $ | |
| describe "Deleting" $ do | |
| diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs | |
| index cba8d44..71a92b0 100644 | |
| --- a/test/Feature/InsertSpec.hs | |
| +++ b/test/Feature/InsertSpec.hs | |
| @@ -10,6 +10,7 @@ import PostgREST.Types (DbStructure(..)) | |
| import qualified Data.Aeson as JSON | |
| import Data.Maybe (fromJust) | |
| +import Data.Pool | |
| import Text.Heredoc | |
| import Network.HTTP.Types.Header | |
| import Network.HTTP.Types | |
| @@ -18,7 +19,7 @@ import qualified Hasql.Connection as H | |
| import TestTypes(IncPK(..), CompoundPK(..)) | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = beforeAll_ resetDb $ around (withApp cfgDefault struct c) $ do | |
| describe "Posting new record" $ do | |
| context "disparate csv types" $ do | |
| diff --git a/test/Feature/QueryLimitedSpec.hs b/test/Feature/QueryLimitedSpec.hs | |
| index 71e6aa7..5ba81e6 100644 | |
| --- a/test/Feature/QueryLimitedSpec.hs | |
| +++ b/test/Feature/QueryLimitedSpec.hs | |
| @@ -6,11 +6,12 @@ import Test.Hspec.Wai.JSON | |
| import Network.HTTP.Types | |
| import Network.Wai.Test (SResponse(simpleHeaders, simpleStatus)) | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = | |
| beforeAll resetDb | |
| . around (withApp (cfgLimitRows 3) struct c) $ | |
| diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs | |
| index 4de2b60..913879e 100644 | |
| --- a/test/Feature/QuerySpec.hs | |
| +++ b/test/Feature/QuerySpec.hs | |
| @@ -6,21 +6,22 @@ import Test.Hspec.Wai.JSON | |
| import Network.HTTP.Types | |
| import Network.Wai.Test (SResponse(simpleHeaders)) | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| import Text.Heredoc | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = around (withApp cfgDefault struct c) $ do | |
| - describe "Querying a table with a column called count" $ | |
| - it "should not confuse count column with pg_catalog.count aggregate" $ | |
| - get "/has_count_column" `shouldRespondWith` 200 | |
| + -- describe "Querying a table with a column called count" $ | |
| + -- it "should not confuse count column with pg_catalog.count aggregate" $ | |
| + -- get "/has_count_column" `shouldRespondWith` 200 | |
| - describe "Querying a nonexistent table" $ | |
| - it "causes a 404" $ | |
| - get "/faketable" `shouldRespondWith` 404 | |
| + -- describe "Querying a nonexistent table" $ | |
| + -- it "causes a 404" $ | |
| + -- get "/faketable" `shouldRespondWith` 404 | |
| describe "Filtering response" $ do | |
| it "matches with equality" $ | |
| @@ -39,364 +40,364 @@ spec struct c = around (withApp cfgDefault struct c) $ do | |
| , matchHeaders = ["Content-Range" <:> "0-13/14"] | |
| } | |
| - it "matches with more than one condition using not operator" $ | |
| - get "/simple_pk?k=like.*yx&extra=not.eq.u" `shouldRespondWith` "[]" | |
| - | |
| - it "matches with inequality using not operator" $ do | |
| - get "/items?id=not.lt.14&order=id.asc" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":14},{"id":15}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| - } | |
| - get "/items?id=not.gt.2&order=id.asc" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":1},{"id":2}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| - } | |
| - | |
| - it "matches items IN" $ | |
| - get "/items?id=in.1,3,5" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "matches items NOT IN" $ | |
| - get "/items?id=notin.2,4,6,7,8,9,10,11,12,13,14,15" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "matches items NOT IN using not operator" $ | |
| - get "/items?id=not.in.2,4,6,7,8,9,10,11,12,13,14,15" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "matches nulls using not operator" $ | |
| - get "/no_pk?a=not.is.null" `shouldRespondWith` | |
| - [json| [{"a":"1","b":"0"},{"a":"2","b":"0"}] |] | |
| - | |
| - it "matches nulls in varchar and numeric fields alike" $ do | |
| - get "/no_pk?a=is.null" `shouldRespondWith` | |
| - [json| [{"a": null, "b": null}] |] | |
| - | |
| - get "/nullable_integer?a=is.null" `shouldRespondWith` [str|[{"a":null}]|] | |
| - | |
| - it "matches with like" $ do | |
| - get "/simple_pk?k=like.*yx" `shouldRespondWith` | |
| - [str|[{"k":"xyyx","extra":"u"}]|] | |
| - get "/simple_pk?k=like.xy*" `shouldRespondWith` | |
| - [str|[{"k":"xyyx","extra":"u"}]|] | |
| - get "/simple_pk?k=like.*YY*" `shouldRespondWith` | |
| - [str|[{"k":"xYYx","extra":"v"}]|] | |
| - | |
| - it "matches with like using not operator" $ | |
| - get "/simple_pk?k=not.like.*yx" `shouldRespondWith` | |
| - [str|[{"k":"xYYx","extra":"v"}]|] | |
| - | |
| - it "matches with ilike" $ do | |
| - get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith` | |
| - [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] | |
| - get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith` | |
| - [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] | |
| - | |
| - it "matches with ilike using not operator" $ | |
| - get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]" | |
| - | |
| - it "matches with tsearch @@" $ | |
| - get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith` | |
| - [json| [{"text_search_vector":"'bar':2 'foo':1"}] |] | |
| - | |
| - it "matches with tsearch @@ using not operator" $ | |
| - get "/tsearch?text_search_vector=not.@@.foo" `shouldRespondWith` | |
| - [json| [{"text_search_vector":"'baz':1 'qux':2"}] |] | |
| - | |
| - it "matches with computed column" $ | |
| - get "/items?always_true=eq.true&order=id.asc" `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}] |] | |
| - | |
| - it "order by computed column" $ | |
| - get "/items?order=anti_id.desc" `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}] |] | |
| - | |
| - it "matches filtering nested items" $ | |
| - get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith` | |
| - [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1,"name":"Design w7"}]},{"id":2,"tasks":[{"id":3,"name":"Design w10"}]}]},{"id":2,"projects":[{"id":3,"tasks":[{"id":5,"name":"Design IOS"}]},{"id":4,"tasks":[{"id":7,"name":"Design OSX"}]}]}]|] | |
| - | |
| - it "matches with @> operator" $ | |
| - get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith` | |
| - [str|[{"id":2},{"id":3}]|] | |
| - | |
| - it "matches with <@ operator" $ | |
| - get "/complex_items?select=id&arr_data=<@.{1,2,4}" `shouldRespondWith` | |
| - [str|[{"id":1},{"id":2}]|] | |
| - | |
| - | |
| - describe "Shaping response with select parameter" $ do | |
| - | |
| - it "selectStar works in absense of parameter" $ | |
| - get "/complex_items?id=eq.3" `shouldRespondWith` | |
| - [str|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3]}]|] | |
| - | |
| - it "one simple column" $ | |
| - get "/complex_items?select=id" `shouldRespondWith` | |
| - [json| [{"id":1},{"id":2},{"id":3}] |] | |
| - | |
| - it "one simple column with casting (text)" $ | |
| - get "/complex_items?select=id::text" `shouldRespondWith` | |
| - [json| [{"id":"1"},{"id":"2"},{"id":"3"}] |] | |
| - | |
| - it "json column" $ | |
| - get "/complex_items?id=eq.1&select=settings" `shouldRespondWith` | |
| - [json| [{"settings":{"foo":{"int":1,"bar":"baz"}}}] |] | |
| - | |
| - it "json subfield one level with casting (json)" $ | |
| - get "/complex_items?id=eq.1&select=settings->>foo::json" `shouldRespondWith` | |
| - [json| [{"foo":{"int":1,"bar":"baz"}}] |] -- the value of foo here is of type "text" | |
| - | |
| - it "fails on bad casting (data of the wrong format)" $ | |
| - get "/complex_items?select=settings->foo->>bar::integer" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| {"hint":null,"details":null,"code":"22P02","message":"invalid input syntax for integer: \"baz\""} |] | |
| - , matchStatus = 400 | |
| - , matchHeaders = [] | |
| - } | |
| - | |
| - it "fails on bad casting (wrong cast type)" $ | |
| - get "/complex_items?select=id::fakecolumntype" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| {"hint":null,"details":null,"code":"42704","message":"type \"fakecolumntype\" does not exist"} |] | |
| - , matchStatus = 400 | |
| - , matchHeaders = [] | |
| - } | |
| - | |
| - | |
| - it "json subfield two levels (string)" $ | |
| - get "/complex_items?id=eq.1&select=settings->foo->>bar" `shouldRespondWith` | |
| - [json| [{"bar":"baz"}] |] | |
| - | |
| - | |
| - it "json subfield two levels with casting (int)" $ | |
| - get "/complex_items?id=eq.1&select=settings->foo->>int::integer" `shouldRespondWith` | |
| - [json| [{"int":1}] |] -- the value in the db is an int, but here we expect a string for now | |
| - | |
| - it "requesting parents and children" $ | |
| - get "/projects?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith` | |
| - [str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] | |
| - | |
| - it "requesting parents and filtering parent columns" $ | |
| - get "/projects?id=eq.1&select=id, name, clients{id}" `shouldRespondWith` | |
| - [str|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|] | |
| - | |
| - it "rows with missing parents are included" $ | |
| - get "/projects?id=in.1,5&select=id,clients{id}" `shouldRespondWith` | |
| - [str|[{"id":1,"clients":{"id":1}},{"id":5,"clients":null}]|] | |
| - | |
| - it "rows with no children return [] instead of null" $ | |
| - get "/projects?id=in.5&select=id,tasks{id}" `shouldRespondWith` | |
| - [str|[{"id":5,"tasks":[]}]|] | |
| - | |
| - it "requesting children 2 levels" $ | |
| - get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith` | |
| - [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] | |
| - | |
| - it "requesting many<->many relation" $ | |
| - get "/tasks?select=id,users{id}" `shouldRespondWith` | |
| - [str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|] | |
| - | |
| - | |
| - it "requesting many<->many relation reverse" $ | |
| - get "/users?select=id,tasks{id}" `shouldRespondWith` | |
| - [str|[{"id":1,"tasks":[{"id":1},{"id":2},{"id":3},{"id":4}]},{"id":2,"tasks":[{"id":5},{"id":6},{"id":7}]},{"id":3,"tasks":[{"id":1},{"id":5}]}]|] | |
| - | |
| - it "requesting parents and children on views" $ | |
| - get "/projects_view?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith` | |
| - [str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] | |
| - | |
| - it "requesting children with composite key" $ | |
| - get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith` | |
| - [str|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] | |
| - | |
| - it "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` | |
| - [str|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] | |
| - | |
| - it "can select by column name" $ | |
| - get "/projects?id=in.1,3&select=id,name,client_id,client_id{id,name}" `shouldRespondWith` | |
| - [str|[{"id":1,"name":"Windows 7","client_id":1,"client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client_id":{"id":2,"name":"Apple"}}]|] | |
| - | |
| - it "can select by column name sans id" $ | |
| - get "/projects?id=in.1,3&select=id,name,client_id,client{id,name}" `shouldRespondWith` | |
| - [str|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|] | |
| - | |
| - | |
| - describe "Plurality singular" $ do | |
| - it "will select an existing object" $ | |
| - request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] "" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| {"id":5} |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = [] | |
| - } | |
| - | |
| - it "works in the presence of a range header" $ | |
| - let headers = ("Prefer","plurality=singular") : | |
| - rangeHdrs (ByteRangeFromTo 0 9) in | |
| - request methodGet "/items" headers "" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| {"id":1} |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = [] | |
| - } | |
| - | |
| - it "will respond with 404 when not found" $ | |
| - request methodGet "/items?id=eq.9999" [("Prefer","plurality=singular")] "" | |
| - `shouldRespondWith` 404 | |
| - | |
| - it "can shape plurality singular object routes" $ | |
| - request methodGet "/projects_view?id=eq.1&select=id,name,clients{*},tasks{id,name}" [("Prefer","plurality=singular")] "" | |
| - `shouldRespondWith` | |
| - [str|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|] | |
| - | |
| - | |
| - describe "ordering response" $ do | |
| - it "by a column asc" $ | |
| - get "/items?id=lte.2&order=id.asc" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":1},{"id":2}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| - } | |
| - it "by a column desc" $ | |
| - get "/items?id=lte.2&order=id.desc" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"id":2},{"id":1}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| - } | |
| - | |
| - it "by a column asc with nulls last" $ | |
| - get "/no_pk?order=a.asc.nullslast" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"a":"1","b":"0"}, | |
| - {"a":"2","b":"0"}, | |
| - {"a":null,"b":null}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "by a column desc with nulls first" $ | |
| - get "/no_pk?order=a.desc.nullsfirst" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"a":null,"b":null}, | |
| - {"a":"2","b":"0"}, | |
| - {"a":"1","b":"0"}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "by a column desc with nulls last" $ | |
| - get "/no_pk?order=a.desc.nullslast" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just [json| [{"a":"2","b":"0"}, | |
| - {"a":"1","b":"0"}, | |
| - {"a":null,"b":null}] |] | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| - } | |
| - | |
| - it "without other constraints" $ | |
| - get "/items?order=id.asc" `shouldRespondWith` 200 | |
| - | |
| - describe "Accept headers" $ do | |
| - it "should respond an unknown accept type with 415" $ | |
| - request methodGet "/simple_pk" | |
| - (acceptHdrs "text/unknowntype") "" | |
| - `shouldRespondWith` 415 | |
| - | |
| - it "should respond correctly to */* in accept header" $ | |
| - request methodGet "/simple_pk" | |
| - (acceptHdrs "*/*") "" | |
| - `shouldRespondWith` 200 | |
| - | |
| - it "should respond correctly to multiple types in accept header" $ | |
| - request methodGet "/simple_pk" | |
| - (acceptHdrs "text/unknowntype, text/csv") "" | |
| - `shouldRespondWith` 200 | |
| - | |
| - it "should respond with CSV to 'text/csv' request" $ | |
| - request methodGet "/simple_pk" | |
| - (acceptHdrs "text/csv; version=1") "" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just "k,extra\nxyyx,u\nxYYx,v" | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Type" <:> "text/csv"] | |
| - } | |
| - | |
| - describe "Canonical location" $ do | |
| - it "Sets Content-Location with alphabetized params" $ | |
| - get "/no_pk?b=eq.1&a=eq.1" | |
| - `shouldRespondWith` ResponseMatcher { | |
| - matchBody = Just "[]" | |
| - , matchStatus = 200 | |
| - , matchHeaders = ["Content-Location" <:> "/no_pk?a=eq.1&b=eq.1"] | |
| - } | |
| - | |
| - it "Omits question mark when there are no params" $ do | |
| - r <- get "/simple_pk" | |
| - liftIO $ do | |
| - let respHeaders = simpleHeaders r | |
| - respHeaders `shouldSatisfy` matchHeader | |
| - "Content-Location" "/simple_pk" | |
| - | |
| - describe "jsonb" $ do | |
| - it "can filter by properties inside json column" $ do | |
| - get "/json?data->foo->>bar=eq.baz" `shouldRespondWith` | |
| - [json| [{"data": {"id": 1, "foo": {"bar": "baz"}}}] |] | |
| - get "/json?data->foo->>bar=eq.fake" `shouldRespondWith` | |
| - [json| [] |] | |
| - it "can filter by properties inside json column using not" $ | |
| - get "/json?data->foo->>bar=not.eq.baz" `shouldRespondWith` | |
| - [json| [] |] | |
| - it "can filter by properties inside json column using ->>" $ | |
| - get "/json?data->>id=eq.1" `shouldRespondWith` | |
| - [json| [{"data": {"id": 1, "foo": {"bar": "baz"}}}] |] | |
| - | |
| - describe "remote procedure call" $ do | |
| - context "a proc that returns a set" $ | |
| - it "returns proper json" $ | |
| - post "/rpc/getitemrange" [json| { "min": 2, "max": 4 } |] `shouldRespondWith` | |
| - [json| [ {"id": 3}, {"id":4} ] |] | |
| - | |
| - context "a proc that returns an empty rowset" $ | |
| - it "returns empty json array" $ | |
| - post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith` | |
| - [json| [] |] | |
| - | |
| - context "a proc that returns plain text" $ | |
| - it "returns proper json" $ | |
| - post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith` | |
| - [json| [{"sayhello":"Hello, world"}] |] | |
| - | |
| - describe "weird requests" $ do | |
| - it "can query as normal" $ do | |
| - get "/Escap3e;" `shouldRespondWith` | |
| - [json| [{"so6meIdColumn":1},{"so6meIdColumn":2},{"so6meIdColumn":3},{"so6meIdColumn":4},{"so6meIdColumn":5}] |] | |
| - get "/ghostBusters" `shouldRespondWith` | |
| - [json| [{"escapeId":1},{"escapeId":3},{"escapeId":5}] |] | |
| - | |
| - it "will embed a collection" $ | |
| - get "/Escap3e;?select=ghostBusters{*}" `shouldRespondWith` | |
| - [json| [{"ghostBusters":[{"escapeId":1}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":3}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":5}]}] |] | |
| - | |
| - it "will embed using a column" $ | |
| - get "/ghostBusters?select=escapeId{*}" `shouldRespondWith` | |
| - [json| [{"escapeId":{"so6meIdColumn":1}},{"escapeId":{"so6meIdColumn":3}},{"escapeId":{"so6meIdColumn":5}}] |] | |
| + -- it "matches with more than one condition using not operator" $ | |
| + -- get "/simple_pk?k=like.*yx&extra=not.eq.u" `shouldRespondWith` "[]" | |
| + | |
| + -- it "matches with inequality using not operator" $ do | |
| + -- get "/items?id=not.lt.14&order=id.asc" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":14},{"id":15}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| + -- } | |
| + -- get "/items?id=not.gt.2&order=id.asc" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":1},{"id":2}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| + -- } | |
| + | |
| + -- it "matches items IN" $ | |
| + -- get "/items?id=in.1,3,5" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "matches items NOT IN" $ | |
| + -- get "/items?id=notin.2,4,6,7,8,9,10,11,12,13,14,15" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "matches items NOT IN using not operator" $ | |
| + -- get "/items?id=not.in.2,4,6,7,8,9,10,11,12,13,14,15" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":1},{"id":3},{"id":5}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "matches nulls using not operator" $ | |
| + -- get "/no_pk?a=not.is.null" `shouldRespondWith` | |
| + -- [json| [{"a":"1","b":"0"},{"a":"2","b":"0"}] |] | |
| + | |
| + -- it "matches nulls in varchar and numeric fields alike" $ do | |
| + -- get "/no_pk?a=is.null" `shouldRespondWith` | |
| + -- [json| [{"a": null, "b": null}] |] | |
| + | |
| + -- get "/nullable_integer?a=is.null" `shouldRespondWith` [str|[{"a":null}]|] | |
| + | |
| + -- it "matches with like" $ do | |
| + -- get "/simple_pk?k=like.*yx" `shouldRespondWith` | |
| + -- [str|[{"k":"xyyx","extra":"u"}]|] | |
| + -- get "/simple_pk?k=like.xy*" `shouldRespondWith` | |
| + -- [str|[{"k":"xyyx","extra":"u"}]|] | |
| + -- get "/simple_pk?k=like.*YY*" `shouldRespondWith` | |
| + -- [str|[{"k":"xYYx","extra":"v"}]|] | |
| + | |
| + -- it "matches with like using not operator" $ | |
| + -- get "/simple_pk?k=not.like.*yx" `shouldRespondWith` | |
| + -- [str|[{"k":"xYYx","extra":"v"}]|] | |
| + | |
| + -- it "matches with ilike" $ do | |
| + -- get "/simple_pk?k=ilike.xy*&order=extra.asc" `shouldRespondWith` | |
| + -- [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] | |
| + -- get "/simple_pk?k=ilike.*YY*&order=extra.asc" `shouldRespondWith` | |
| + -- [str|[{"k":"xyyx","extra":"u"},{"k":"xYYx","extra":"v"}]|] | |
| + | |
| + -- it "matches with ilike using not operator" $ | |
| + -- get "/simple_pk?k=not.ilike.xy*&order=extra.asc" `shouldRespondWith` "[]" | |
| + | |
| + -- it "matches with tsearch @@" $ | |
| + -- get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith` | |
| + -- [json| [{"text_search_vector":"'bar':2 'foo':1"}] |] | |
| + | |
| + -- it "matches with tsearch @@ using not operator" $ | |
| + -- get "/tsearch?text_search_vector=not.@@.foo" `shouldRespondWith` | |
| + -- [json| [{"text_search_vector":"'baz':1 'qux':2"}] |] | |
| + | |
| + -- it "matches with computed column" $ | |
| + -- get "/items?always_true=eq.true&order=id.asc" `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}] |] | |
| + | |
| + -- it "order by computed column" $ | |
| + -- get "/items?order=anti_id.desc" `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}] |] | |
| + | |
| + -- it "matches filtering nested items" $ | |
| + -- get "/clients?select=id,projects{id,tasks{id,name}}&projects.tasks.name=like.Design*" `shouldRespondWith` | |
| + -- [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1,"name":"Design w7"}]},{"id":2,"tasks":[{"id":3,"name":"Design w10"}]}]},{"id":2,"projects":[{"id":3,"tasks":[{"id":5,"name":"Design IOS"}]},{"id":4,"tasks":[{"id":7,"name":"Design OSX"}]}]}]|] | |
| + | |
| + -- it "matches with @> operator" $ | |
| + -- get "/complex_items?select=id&arr_data=@>.{2}" `shouldRespondWith` | |
| + -- [str|[{"id":2},{"id":3}]|] | |
| + | |
| + -- it "matches with <@ operator" $ | |
| + -- get "/complex_items?select=id&arr_data=<@.{1,2,4}" `shouldRespondWith` | |
| + -- [str|[{"id":1},{"id":2}]|] | |
| + | |
| + | |
| + -- describe "Shaping response with select parameter" $ do | |
| + | |
| + -- it "selectStar works in absense of parameter" $ | |
| + -- get "/complex_items?id=eq.3" `shouldRespondWith` | |
| + -- [str|[{"id":3,"name":"Three","settings":{"foo":{"int":1,"bar":"baz"}},"arr_data":[1,2,3]}]|] | |
| + | |
| + -- it "one simple column" $ | |
| + -- get "/complex_items?select=id" `shouldRespondWith` | |
| + -- [json| [{"id":1},{"id":2},{"id":3}] |] | |
| + | |
| + -- it "one simple column with casting (text)" $ | |
| + -- get "/complex_items?select=id::text" `shouldRespondWith` | |
| + -- [json| [{"id":"1"},{"id":"2"},{"id":"3"}] |] | |
| + | |
| + -- it "json column" $ | |
| + -- get "/complex_items?id=eq.1&select=settings" `shouldRespondWith` | |
| + -- [json| [{"settings":{"foo":{"int":1,"bar":"baz"}}}] |] | |
| + | |
| + -- it "json subfield one level with casting (json)" $ | |
| + -- get "/complex_items?id=eq.1&select=settings->>foo::json" `shouldRespondWith` | |
| + -- [json| [{"foo":{"int":1,"bar":"baz"}}] |] -- the value of foo here is of type "text" | |
| + | |
| + -- it "fails on bad casting (data of the wrong format)" $ | |
| + -- get "/complex_items?select=settings->foo->>bar::integer" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| {"hint":null,"details":null,"code":"22P02","message":"invalid input syntax for integer: \"baz\""} |] | |
| + -- , matchStatus = 400 | |
| + -- , matchHeaders = [] | |
| + -- } | |
| + | |
| + -- it "fails on bad casting (wrong cast type)" $ | |
| + -- get "/complex_items?select=id::fakecolumntype" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| {"hint":null,"details":null,"code":"42704","message":"type \"fakecolumntype\" does not exist"} |] | |
| + -- , matchStatus = 400 | |
| + -- , matchHeaders = [] | |
| + -- } | |
| + | |
| + | |
| + -- it "json subfield two levels (string)" $ | |
| + -- get "/complex_items?id=eq.1&select=settings->foo->>bar" `shouldRespondWith` | |
| + -- [json| [{"bar":"baz"}] |] | |
| + | |
| + | |
| + -- it "json subfield two levels with casting (int)" $ | |
| + -- get "/complex_items?id=eq.1&select=settings->foo->>int::integer" `shouldRespondWith` | |
| + -- [json| [{"int":1}] |] -- the value in the db is an int, but here we expect a string for now | |
| + | |
| + -- it "requesting parents and children" $ | |
| + -- get "/projects?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] | |
| + | |
| + -- it "requesting parents and filtering parent columns" $ | |
| + -- get "/projects?id=eq.1&select=id, name, clients{id}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"name":"Windows 7","clients":{"id":1}}]|] | |
| + | |
| + -- it "rows with missing parents are included" $ | |
| + -- get "/projects?id=in.1,5&select=id,clients{id}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"clients":{"id":1}},{"id":5,"clients":null}]|] | |
| + | |
| + -- it "rows with no children return [] instead of null" $ | |
| + -- get "/projects?id=in.5&select=id,tasks{id}" `shouldRespondWith` | |
| + -- [str|[{"id":5,"tasks":[]}]|] | |
| + | |
| + -- it "requesting children 2 levels" $ | |
| + -- get "/clients?id=eq.1&select=id,projects{id,tasks{id}}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"projects":[{"id":1,"tasks":[{"id":1},{"id":2}]},{"id":2,"tasks":[{"id":3},{"id":4}]}]}]|] | |
| + | |
| + -- it "requesting many<->many relation" $ | |
| + -- get "/tasks?select=id,users{id}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"users":[{"id":1},{"id":3}]},{"id":2,"users":[{"id":1}]},{"id":3,"users":[{"id":1}]},{"id":4,"users":[{"id":1}]},{"id":5,"users":[{"id":2},{"id":3}]},{"id":6,"users":[{"id":2}]},{"id":7,"users":[{"id":2}]},{"id":8,"users":[]}]|] | |
| + | |
| + | |
| + -- it "requesting many<->many relation reverse" $ | |
| + -- get "/users?select=id,tasks{id}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"tasks":[{"id":1},{"id":2},{"id":3},{"id":4}]},{"id":2,"tasks":[{"id":5},{"id":6},{"id":7}]},{"id":3,"tasks":[{"id":1},{"id":5}]}]|] | |
| + | |
| + -- it "requesting parents and children on views" $ | |
| + -- get "/projects_view?id=eq.1&select=id, name, clients{*}, tasks{id, name}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}]|] | |
| + | |
| + -- it "requesting children with composite key" $ | |
| + -- get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith` | |
| + -- [str|[{"user_id":2,"task_id":6,"comments":[{"content":"Needs to be delivered ASAP"}]}]|] | |
| + | |
| + -- it "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` | |
| + -- [str|[{"id":1,"articleStars":[{"users":{"id":1,"name":"Angela Martin"}},{"users":{"id":2,"name":"Michael Scott"}},{"users":{"id":3,"name":"Dwight Schrute"}}]}]|] | |
| + | |
| + -- it "can select by column name" $ | |
| + -- get "/projects?id=in.1,3&select=id,name,client_id,client_id{id,name}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"name":"Windows 7","client_id":1,"client_id":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client_id":{"id":2,"name":"Apple"}}]|] | |
| + | |
| + -- it "can select by column name sans id" $ | |
| + -- get "/projects?id=in.1,3&select=id,name,client_id,client{id,name}" `shouldRespondWith` | |
| + -- [str|[{"id":1,"name":"Windows 7","client_id":1,"client":{"id":1,"name":"Microsoft"}},{"id":3,"name":"IOS","client_id":2,"client":{"id":2,"name":"Apple"}}]|] | |
| + | |
| + | |
| + -- describe "Plurality singular" $ do | |
| + -- it "will select an existing object" $ | |
| + -- request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] "" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| {"id":5} |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = [] | |
| + -- } | |
| + | |
| + -- it "works in the presence of a range header" $ | |
| + -- let headers = ("Prefer","plurality=singular") : | |
| + -- rangeHdrs (ByteRangeFromTo 0 9) in | |
| + -- request methodGet "/items" headers "" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| {"id":1} |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = [] | |
| + -- } | |
| + | |
| + -- it "will respond with 404 when not found" $ | |
| + -- request methodGet "/items?id=eq.9999" [("Prefer","plurality=singular")] "" | |
| + -- `shouldRespondWith` 404 | |
| + | |
| + -- it "can shape plurality singular object routes" $ | |
| + -- request methodGet "/projects_view?id=eq.1&select=id,name,clients{*},tasks{id,name}" [("Prefer","plurality=singular")] "" | |
| + -- `shouldRespondWith` | |
| + -- [str|{"id":1,"name":"Windows 7","clients":{"id":1,"name":"Microsoft"},"tasks":[{"id":1,"name":"Design w7"},{"id":2,"name":"Code w7"}]}|] | |
| + | |
| + | |
| + -- describe "ordering response" $ do | |
| + -- it "by a column asc" $ | |
| + -- get "/items?id=lte.2&order=id.asc" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":1},{"id":2}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| + -- } | |
| + -- it "by a column desc" $ | |
| + -- get "/items?id=lte.2&order=id.desc" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"id":2},{"id":1}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-1/2"] | |
| + -- } | |
| + | |
| + -- it "by a column asc with nulls last" $ | |
| + -- get "/no_pk?order=a.asc.nullslast" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"a":"1","b":"0"}, | |
| + -- {"a":"2","b":"0"}, | |
| + -- {"a":null,"b":null}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "by a column desc with nulls first" $ | |
| + -- get "/no_pk?order=a.desc.nullsfirst" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"a":null,"b":null}, | |
| + -- {"a":"2","b":"0"}, | |
| + -- {"a":"1","b":"0"}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "by a column desc with nulls last" $ | |
| + -- get "/no_pk?order=a.desc.nullslast" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just [json| [{"a":"2","b":"0"}, | |
| + -- {"a":"1","b":"0"}, | |
| + -- {"a":null,"b":null}] |] | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Range" <:> "0-2/3"] | |
| + -- } | |
| + | |
| + -- it "without other constraints" $ | |
| + -- get "/items?order=id.asc" `shouldRespondWith` 200 | |
| + | |
| + -- describe "Accept headers" $ do | |
| + -- it "should respond an unknown accept type with 415" $ | |
| + -- request methodGet "/simple_pk" | |
| + -- (acceptHdrs "text/unknowntype") "" | |
| + -- `shouldRespondWith` 415 | |
| + | |
| + -- it "should respond correctly to */* in accept header" $ | |
| + -- request methodGet "/simple_pk" | |
| + -- (acceptHdrs "*/*") "" | |
| + -- `shouldRespondWith` 200 | |
| + | |
| + -- it "should respond correctly to multiple types in accept header" $ | |
| + -- request methodGet "/simple_pk" | |
| + -- (acceptHdrs "text/unknowntype, text/csv") "" | |
| + -- `shouldRespondWith` 200 | |
| + | |
| + -- it "should respond with CSV to 'text/csv' request" $ | |
| + -- request methodGet "/simple_pk" | |
| + -- (acceptHdrs "text/csv; version=1") "" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just "k,extra\nxyyx,u\nxYYx,v" | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Type" <:> "text/csv"] | |
| + -- } | |
| + | |
| + -- describe "Canonical location" $ do | |
| + -- it "Sets Content-Location with alphabetized params" $ | |
| + -- get "/no_pk?b=eq.1&a=eq.1" | |
| + -- `shouldRespondWith` ResponseMatcher { | |
| + -- matchBody = Just "[]" | |
| + -- , matchStatus = 200 | |
| + -- , matchHeaders = ["Content-Location" <:> "/no_pk?a=eq.1&b=eq.1"] | |
| + -- } | |
| + | |
| + -- it "Omits question mark when there are no params" $ do | |
| + -- r <- get "/simple_pk" | |
| + -- liftIO $ do | |
| + -- let respHeaders = simpleHeaders r | |
| + -- respHeaders `shouldSatisfy` matchHeader | |
| + -- "Content-Location" "/simple_pk" | |
| + | |
| + -- describe "jsonb" $ do | |
| + -- it "can filter by properties inside json column" $ do | |
| + -- get "/json?data->foo->>bar=eq.baz" `shouldRespondWith` | |
| + -- [json| [{"data": {"id": 1, "foo": {"bar": "baz"}}}] |] | |
| + -- get "/json?data->foo->>bar=eq.fake" `shouldRespondWith` | |
| + -- [json| [] |] | |
| + -- it "can filter by properties inside json column using not" $ | |
| + -- get "/json?data->foo->>bar=not.eq.baz" `shouldRespondWith` | |
| + -- [json| [] |] | |
| + -- it "can filter by properties inside json column using ->>" $ | |
| + -- get "/json?data->>id=eq.1" `shouldRespondWith` | |
| + -- [json| [{"data": {"id": 1, "foo": {"bar": "baz"}}}] |] | |
| + | |
| + -- describe "remote procedure call" $ do | |
| + -- context "a proc that returns a set" $ | |
| + -- it "returns proper json" $ | |
| + -- post "/rpc/getitemrange" [json| { "min": 2, "max": 4 } |] `shouldRespondWith` | |
| + -- [json| [ {"id": 3}, {"id":4} ] |] | |
| + | |
| + -- context "a proc that returns an empty rowset" $ | |
| + -- it "returns empty json array" $ | |
| + -- post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith` | |
| + -- [json| [] |] | |
| + | |
| + -- context "a proc that returns plain text" $ | |
| + -- it "returns proper json" $ | |
| + -- post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith` | |
| + -- [json| [{"sayhello":"Hello, world"}] |] | |
| + | |
| + -- describe "weird requests" $ do | |
| + -- it "can query as normal" $ do | |
| + -- get "/Escap3e;" `shouldRespondWith` | |
| + -- [json| [{"so6meIdColumn":1},{"so6meIdColumn":2},{"so6meIdColumn":3},{"so6meIdColumn":4},{"so6meIdColumn":5}] |] | |
| + -- get "/ghostBusters" `shouldRespondWith` | |
| + -- [json| [{"escapeId":1},{"escapeId":3},{"escapeId":5}] |] | |
| + | |
| + -- it "will embed a collection" $ | |
| + -- get "/Escap3e;?select=ghostBusters{*}" `shouldRespondWith` | |
| + -- [json| [{"ghostBusters":[{"escapeId":1}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":3}]},{"ghostBusters":[]},{"ghostBusters":[{"escapeId":5}]}] |] | |
| + | |
| + -- it "will embed using a column" $ | |
| + -- get "/ghostBusters?select=escapeId{*}" `shouldRespondWith` | |
| + -- [json| [{"escapeId":{"so6meIdColumn":1}},{"escapeId":{"so6meIdColumn":3}},{"escapeId":{"so6meIdColumn":5}}] |] | |
| diff --git a/test/Feature/RangeSpec.hs b/test/Feature/RangeSpec.hs | |
| index 0ab9987..2c941ba 100644 | |
| --- a/test/Feature/RangeSpec.hs | |
| +++ b/test/Feature/RangeSpec.hs | |
| @@ -6,11 +6,12 @@ import Test.Hspec.Wai.JSON | |
| import Network.HTTP.Types | |
| import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus)) | |
| import qualified Hasql.Connection as H | |
| +import Data.Pool | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = beforeAll resetDb | |
| . around (withApp cfgDefault struct c) $ | |
| describe "GET /items" $ do | |
| diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs | |
| index 9d8834f..7c75cb8 100644 | |
| --- a/test/Feature/StructureSpec.hs | |
| +++ b/test/Feature/StructureSpec.hs | |
| @@ -7,10 +7,11 @@ import qualified Hasql.Connection as H | |
| import SpecHelper | |
| import PostgREST.Types (DbStructure(..)) | |
| +import Data.Pool | |
| import Network.HTTP.Types | |
| -spec :: DbStructure -> H.Connection -> Spec | |
| +spec :: DbStructure -> Pool (Either H.ConnectionError H.Connection) -> Spec | |
| spec struct c = around (withApp cfgDefault struct c) $ do | |
| describe "GET /" $ do | |
| it "lists views in schema" $ | |
| diff --git a/test/Main.hs b/test/Main.hs | |
| index 6256f5d..6a42c2b 100644 | |
| --- a/test/Main.hs | |
| +++ b/test/Main.hs | |
| @@ -7,7 +7,9 @@ import qualified Hasql.Session as H | |
| import qualified Hasql.Connection as H | |
| import PostgREST.DbStructure (getDbStructure) | |
| +import PostgREST.Types (DbStructure(..)) | |
| import Data.String.Conversions (cs) | |
| +import Data.Pool | |
| import qualified Feature.AuthSpec | |
| import qualified Feature.CorsSpec | |
| @@ -20,24 +22,28 @@ import qualified Feature.StructureSpec | |
| main :: IO () | |
| main = do | |
| - setupDb | |
| + --setupDb | |
| - H.acquire (cs dbString) >>= \case | |
| - Left err -> error $ show err | |
| - Right c -> do | |
| - dbOrErr <- H.run (getDbStructure "test") c | |
| - -- Not using hspec-discover because we want to precompute | |
| - -- the db structure and pass it to specs for speed | |
| - either (error.show) (hspec . specs c) dbOrErr | |
| - H.release c | |
| + pool <- createPool (H.acquire . cs $ dbString) | |
| + (either (const $ return ()) H.release) 1 1 2 | |
| + hspec $ specs pool (DbStructure [] [] [] []) | |
| + | |
| + -- H.acquire (cs dbString) >>= \case | |
| + -- Left err -> error $ show err | |
| + -- Right c -> do | |
| + -- dbOrErr <- H.run (getDbStructure "test") c | |
| + -- -- Not using hspec-discover because we want to precompute | |
| + -- -- the db structure and pass it to specs for speed | |
| + -- either (error.show) (hspec . specs c) dbOrErr | |
| + -- H.release c | |
| where | |
| - specs conn dbStructure = do | |
| - describe "Feature.AuthSpec" $ Feature.AuthSpec.spec dbStructure conn | |
| - describe "Feature.CorsSpec" $ Feature.CorsSpec.spec dbStructure conn | |
| - describe "Feature.DeleteSpec" $ Feature.DeleteSpec.spec dbStructure conn | |
| - describe "Feature.InsertSpec" $ Feature.InsertSpec.spec dbStructure conn | |
| - describe "Feature.QueryLimitedSpec" $ Feature.QueryLimitedSpec.spec dbStructure conn | |
| - describe "Feature.QuerySpec" $ Feature.QuerySpec.spec dbStructure conn | |
| - describe "Feature.RangeSpec" $ Feature.RangeSpec.spec dbStructure conn | |
| - describe "Feature.StructureSpec" $ Feature.StructureSpec.spec dbStructure conn | |
| + specs pool dbStructure = do | |
| + -- describe "Feature.AuthSpec" $ Feature.AuthSpec.spec dbStructure conn | |
| + -- describe "Feature.CorsSpec" $ Feature.CorsSpec.spec dbStructure conn | |
| + -- describe "Feature.DeleteSpec" $ Feature.DeleteSpec.spec dbStructure conn | |
| + -- describe "Feature.InsertSpec" $ Feature.InsertSpec.spec dbStructure conn | |
| + -- describe "Feature.QueryLimitedSpec" $ Feature.QueryLimitedSpec.spec dbStructure conn | |
| + describe "Feature.QuerySpec" $ Feature.QuerySpec.spec dbStructure pool | |
| + -- describe "Feature.RangeSpec" $ Feature.RangeSpec.spec dbStructure conn | |
| + -- describe "Feature.StructureSpec" $ Feature.StructureSpec.spec dbStructure conn | |
| diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs | |
| index 4d53839..d885aaf 100644 | |
| --- a/test/SpecHelper.hs | |
| +++ b/test/SpecHelper.hs | |
| @@ -38,16 +38,20 @@ cfgDefault = cfg dbString Nothing | |
| cfgLimitRows :: Integer -> AppConfig | |
| cfgLimitRows = cfg dbString . Just | |
| -withApp :: AppConfig -> DbStructure -> H.Connection | |
| +withApp :: AppConfig -> DbStructure | |
| + -> Pool (Either H.ConnectionError H.Connection) | |
| -> ActionWith Application -> IO () | |
| -withApp config dbStructure c perform = do | |
| +withApp config dbStructure pool perform = do | |
| perform $ defaultMiddle $ \req resp -> do | |
| time <- getPOSIXTime | |
| body <- strictRequestBody req | |
| let handleReq = H.run (runWithClaims config time (app dbStructure config body) req) | |
| - resOrError <- handleReq c | |
| - either (resp . pgErrResponse) resp resOrError | |
| + withResource pool $ \case | |
| + Left err -> error $ show err | |
| + Right c -> do | |
| + resOrError <- handleReq c | |
| + either (resp . pgErrResponse) resp resOrError | |
| setupDb :: IO () | |
| setupDb = do |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment