diff --git a/postgrest.cabal b/postgrest.cabal index 54ab5b78c..2fd494f5b 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -158,7 +158,6 @@ Test-Suite spec , PostgREST.RangeQuery , PostgREST.ApiRequest , PostgREST.Types - , Spec , SpecHelper , TestTypes Build-Depends: aeson diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 7569b0ae9..b32cdfca5 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -202,6 +202,98 @@ allColumns tabs = do info.column_default AS default_value, array_to_string(enum_info.vals, ',') AS enum FROM ( + /* + -- CTE based on information_schema.columns to remove the owner filter + */ + WITH columns AS ( + SELECT current_database()::information_schema.sql_identifier AS table_catalog, + nc.nspname::information_schema.sql_identifier AS table_schema, + c.relname::information_schema.sql_identifier AS table_name, + a.attname::information_schema.sql_identifier AS column_name, + a.attnum::information_schema.cardinal_number AS ordinal_position, + pg_get_expr(ad.adbin, ad.adrelid)::information_schema.character_data AS column_default, + CASE + WHEN a.attnotnull OR t.typtype = 'd'::"char" AND t.typnotnull THEN 'NO'::text + ELSE 'YES'::text + END::information_schema.yes_or_no AS is_nullable, + CASE + WHEN t.typtype = 'd'::"char" THEN + CASE + WHEN bt.typelem <> 0::oid AND bt.typlen = (-1) THEN 'ARRAY'::text + WHEN nbt.nspname = 'pg_catalog'::name THEN format_type(t.typbasetype, NULL::integer) + ELSE 'USER-DEFINED'::text + END + ELSE + CASE + WHEN t.typelem <> 0::oid AND t.typlen = (-1) THEN 'ARRAY'::text + WHEN nt.nspname = 'pg_catalog'::name THEN format_type(a.atttypid, NULL::integer) + ELSE 'USER-DEFINED'::text + END + END::information_schema.character_data AS data_type, + information_schema._pg_char_max_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_maximum_length, + information_schema._pg_char_octet_length(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS character_octet_length, + information_schema._pg_numeric_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision, + information_schema._pg_numeric_precision_radix(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_precision_radix, + information_schema._pg_numeric_scale(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS numeric_scale, + information_schema._pg_datetime_precision(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.cardinal_number AS datetime_precision, + information_schema._pg_interval_type(information_schema._pg_truetypid(a.*, t.*), information_schema._pg_truetypmod(a.*, t.*))::information_schema.character_data AS interval_type, + NULL::integer::information_schema.cardinal_number AS interval_precision, + NULL::character varying::information_schema.sql_identifier AS character_set_catalog, + NULL::character varying::information_schema.sql_identifier AS character_set_schema, + NULL::character varying::information_schema.sql_identifier AS character_set_name, + CASE + WHEN nco.nspname IS NOT NULL THEN current_database() + ELSE NULL::name + END::information_schema.sql_identifier AS collation_catalog, + nco.nspname::information_schema.sql_identifier AS collation_schema, + co.collname::information_schema.sql_identifier AS collation_name, + CASE + WHEN t.typtype = 'd'::"char" THEN current_database() + ELSE NULL::name + END::information_schema.sql_identifier AS domain_catalog, + CASE + WHEN t.typtype = 'd'::"char" THEN nt.nspname + ELSE NULL::name + END::information_schema.sql_identifier AS domain_schema, + CASE + WHEN t.typtype = 'd'::"char" THEN t.typname + ELSE NULL::name + END::information_schema.sql_identifier AS domain_name, + current_database()::information_schema.sql_identifier AS udt_catalog, + COALESCE(nbt.nspname, nt.nspname)::information_schema.sql_identifier AS udt_schema, + COALESCE(bt.typname, t.typname)::information_schema.sql_identifier AS udt_name, + NULL::character varying::information_schema.sql_identifier AS scope_catalog, + NULL::character varying::information_schema.sql_identifier AS scope_schema, + NULL::character varying::information_schema.sql_identifier AS scope_name, + NULL::integer::information_schema.cardinal_number AS maximum_cardinality, + a.attnum::information_schema.sql_identifier AS dtd_identifier, + 'NO'::character varying::information_schema.yes_or_no AS is_self_referencing, + 'NO'::character varying::information_schema.yes_or_no AS is_identity, + NULL::character varying::information_schema.character_data AS identity_generation, + NULL::character varying::information_schema.character_data AS identity_start, + NULL::character varying::information_schema.character_data AS identity_increment, + NULL::character varying::information_schema.character_data AS identity_maximum, + NULL::character varying::information_schema.character_data AS identity_minimum, + NULL::character varying::information_schema.yes_or_no AS identity_cycle, + 'NEVER'::character varying::information_schema.character_data AS is_generated, + NULL::character varying::information_schema.character_data AS generation_expression, + CASE + WHEN c.relkind = 'r'::"char" OR (c.relkind = ANY (ARRAY['v'::"char", 'f'::"char"])) AND pg_column_is_updatable(c.oid::regclass, a.attnum, false) THEN 'YES'::text + ELSE 'NO'::text + END::information_schema.yes_or_no AS is_updatable + FROM pg_attribute a + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + JOIN (pg_class c + JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid + JOIN (pg_type t + JOIN pg_namespace nt ON t.typnamespace = nt.oid) ON a.atttypid = t.oid + LEFT JOIN (pg_type bt + JOIN pg_namespace nbt ON bt.typnamespace = nbt.oid) ON t.typtype = 'd'::"char" AND t.typbasetype = bt.oid + LEFT JOIN (pg_collation co + JOIN pg_namespace nco ON co.collnamespace = nco.oid) ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name) + WHERE NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 AND NOT a.attisdropped AND (c.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char"])) + /*--AND (pg_has_role(c.relowner, 'USAGE'::text) OR has_column_privilege(c.oid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/ + ) SELECT table_schema, table_name, @@ -214,7 +306,8 @@ allColumns tabs = do numeric_precision, column_default, udt_name - FROM information_schema.columns + /*-- FROM information_schema.columns*/ + FROM columns WHERE table_schema NOT IN ('pg_catalog', 'information_schema') ) AS info LEFT OUTER JOIN ( @@ -288,19 +381,106 @@ relationFromRow allTabs allCols (rs, rt, rcs, frs, frt, frcs) = allPrimaryKeys :: [Table] -> H.Tx P.Postgres s [PrimaryKey] allPrimaryKeys tabs = do pks <- H.listEx $ [H.stmt| - SELECT - kc.table_schema, - kc.table_name, - kc.column_name - FROM - information_schema.table_constraints tc, - information_schema.key_column_usage kc - WHERE - tc.constraint_type = 'PRIMARY KEY' AND - kc.table_name = tc.table_name AND - kc.table_schema = tc.table_schema AND - kc.constraint_name = tc.constraint_name AND - kc.table_schema NOT IN ('pg_catalog', 'information_schema') + /* + -- CTE to replace information_schema.table_constraints to remove owner limit + */ + WITH tc AS ( + SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, + nc.nspname::information_schema.sql_identifier AS constraint_schema, + c.conname::information_schema.sql_identifier AS constraint_name, + current_database()::information_schema.sql_identifier AS table_catalog, + nr.nspname::information_schema.sql_identifier AS table_schema, + r.relname::information_schema.sql_identifier AS table_name, + CASE c.contype + WHEN 'c'::"char" THEN 'CHECK'::text + WHEN 'f'::"char" THEN 'FOREIGN KEY'::text + WHEN 'p'::"char" THEN 'PRIMARY KEY'::text + WHEN 'u'::"char" THEN 'UNIQUE'::text + ELSE NULL::text + END::information_schema.character_data AS constraint_type, + CASE + WHEN c.condeferrable THEN 'YES'::text + ELSE 'NO'::text + END::information_schema.yes_or_no AS is_deferrable, + CASE + WHEN c.condeferred THEN 'YES'::text + ELSE 'NO'::text + END::information_schema.yes_or_no AS initially_deferred + FROM pg_namespace nc, + pg_namespace nr, + pg_constraint c, + pg_class r + WHERE nc.oid = c.connamespace AND nr.oid = r.relnamespace AND c.conrelid = r.oid AND (c.contype <> ALL (ARRAY['t'::"char", 'x'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid) + /*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/ + UNION ALL + SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, + nr.nspname::information_schema.sql_identifier AS constraint_schema, + (((((nr.oid::text || '_'::text) || r.oid::text) || '_'::text) || a.attnum::text) || '_not_null'::text)::information_schema.sql_identifier AS constraint_name, + current_database()::information_schema.sql_identifier AS table_catalog, + nr.nspname::information_schema.sql_identifier AS table_schema, + r.relname::information_schema.sql_identifier AS table_name, + 'CHECK'::character varying::information_schema.character_data AS constraint_type, + 'NO'::character varying::information_schema.yes_or_no AS is_deferrable, + 'NO'::character varying::information_schema.yes_or_no AS initially_deferred + FROM pg_namespace nr, + pg_class r, + pg_attribute a + WHERE nr.oid = r.relnamespace AND r.oid = a.attrelid AND a.attnotnull AND a.attnum > 0 AND NOT a.attisdropped AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid) + /*--AND (pg_has_role(r.relowner, 'USAGE'::text) OR has_table_privilege(r.oid, 'INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'::text) OR has_any_column_privilege(r.oid, 'INSERT, UPDATE, REFERENCES'::text))*/ + ), + /* + -- CTE to replace information_schema.key_column_usage to remove owner limit + */ + kc AS ( + SELECT current_database()::information_schema.sql_identifier AS constraint_catalog, + ss.nc_nspname::information_schema.sql_identifier AS constraint_schema, + ss.conname::information_schema.sql_identifier AS constraint_name, + current_database()::information_schema.sql_identifier AS table_catalog, + ss.nr_nspname::information_schema.sql_identifier AS table_schema, + ss.relname::information_schema.sql_identifier AS table_name, + a.attname::information_schema.sql_identifier AS column_name, + (ss.x).n::information_schema.cardinal_number AS ordinal_position, + CASE + WHEN ss.contype = 'f'::"char" THEN information_schema._pg_index_position(ss.conindid, ss.confkey[(ss.x).n]) + ELSE NULL::integer + END::information_schema.cardinal_number AS position_in_unique_constraint + FROM pg_attribute a, + ( SELECT r.oid AS roid, + r.relname, + r.relowner, + nc.nspname AS nc_nspname, + nr.nspname AS nr_nspname, + c.oid AS coid, + c.conname, + c.contype, + c.conindid, + c.confkey, + c.confrelid, + information_schema._pg_expandarray(c.conkey) AS x + FROM pg_namespace nr, + pg_class r, + pg_namespace nc, + pg_constraint c + WHERE nr.oid = r.relnamespace AND r.oid = c.conrelid AND nc.oid = c.connamespace AND (c.contype = ANY (ARRAY['p'::"char", 'u'::"char", 'f'::"char"])) AND r.relkind = 'r'::"char" AND NOT pg_is_other_temp_schema(nr.oid)) ss + WHERE ss.roid = a.attrelid AND a.attnum = (ss.x).x AND NOT a.attisdropped + /*--AND (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text))*/ + ) + SELECT + kc.table_schema, + kc.table_name, + kc.column_name + FROM + /* + --information_schema.table_constraints tc, + --information_schema.key_column_usage kc + */ + tc, kc + WHERE + tc.constraint_type = 'PRIMARY KEY' AND + kc.table_name = tc.table_name AND + kc.table_schema = tc.table_schema AND + kc.constraint_name = tc.constraint_name AND + kc.table_schema NOT IN ('pg_catalog', 'information_schema') |] return $ mapMaybe (pkFromRow tabs) pks diff --git a/test/Feature/AuthSpec.hs b/test/Feature/AuthSpec.hs index 583e569b1..35595153d 100644 --- a/test/Feature/AuthSpec.hs +++ b/test/Feature/AuthSpec.hs @@ -6,11 +6,15 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Network.HTTP.Types +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) -- }}} -spec :: Spec -spec = around (withApp cfgDefault) +spec :: DbStructure -> H.Pool P.Postgres -> 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 1e0da8695..8bc7f600c 100644 --- a/test/Feature/CorsSpec.hs +++ b/test/Feature/CorsSpec.hs @@ -6,13 +6,17 @@ import Test.Hspec.Wai import Network.Wai.Test (SResponse(simpleHeaders, simpleBody)) import qualified Data.ByteString.Lazy as BL +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) import Network.HTTP.Types -- }}} -spec :: Spec -spec = around (withApp cfgDefault) $ describe "CORS" $ do +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = around (withApp cfgDefault struct pool) $ describe "CORS" $ do let preflightHeaders = [ ("Accept", "*/*"), ("Origin", "http://example.com"), diff --git a/test/Feature/DeleteSpec.hs b/test/Feature/DeleteSpec.hs index 263f1f0af..c0f252728 100644 --- a/test/Feature/DeleteSpec.hs +++ b/test/Feature/DeleteSpec.hs @@ -2,13 +2,18 @@ module Feature.DeleteSpec where import Test.Hspec import Test.Hspec.Wai + +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) import Network.HTTP.Types -spec :: Spec -spec = beforeAll resetDb - . around (withApp cfgDefault) $ +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = beforeAll resetDb + . around (withApp cfgDefault struct pool) $ describe "Deleting" $ do context "existing record" $ do it "succeeds with 204 and deletion count" $ diff --git a/test/Feature/InsertSpec.hs b/test/Feature/InsertSpec.hs index e337582cf..38a591153 100644 --- a/test/Feature/InsertSpec.hs +++ b/test/Feature/InsertSpec.hs @@ -5,7 +5,11 @@ import Test.Hspec.Wai import Test.Hspec.Wai.JSON import Network.Wai.Test (SResponse(simpleBody,simpleHeaders,simpleStatus)) +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) import qualified Data.Aeson as JSON import Data.Maybe (fromJust) @@ -16,8 +20,8 @@ import Control.Monad (replicateM_) import TestTypes(IncPK(..), CompoundPK(..)) -spec :: Spec -spec = beforeAll_ resetDb $ around (withApp cfgDefault) $ do +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = beforeAll_ resetDb $ around (withApp cfgDefault struct pool) $ do describe "Posting new record" $ do after_ (clearTable "menagerie") . context "disparate csv types" $ do it "accepts disparate json types" $ do diff --git a/test/Feature/QueryLimitedSpec.hs b/test/Feature/QueryLimitedSpec.hs index 7545282ee..aad13bb3f 100644 --- a/test/Feature/QueryLimitedSpec.hs +++ b/test/Feature/QueryLimitedSpec.hs @@ -6,12 +6,16 @@ import Test.Hspec.Wai.JSON import Network.HTTP.Types import Network.Wai.Test (SResponse(simpleHeaders, simpleStatus)) -import SpecHelper +import Hasql as H +import Hasql.Postgres as P -spec :: Spec -spec = +import SpecHelper +import PostgREST.Types (DbStructure(..)) + +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = beforeAll resetDb - . around (withApp $ cfgLimitRows 3) $ + . around (withApp (cfgLimitRows 3) struct pool) $ describe "Requesting many items with server limits enabled" $ do it "restricts results" $ get "/items" diff --git a/test/Feature/QuerySpec.hs b/test/Feature/QuerySpec.hs index 9b6cd0f04..aadc8edaf 100644 --- a/test/Feature/QuerySpec.hs +++ b/test/Feature/QuerySpec.hs @@ -6,12 +6,15 @@ import Test.Hspec.Wai.JSON import Network.HTTP.Types import Network.Wai.Test (SResponse(simpleHeaders)) +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) import Text.Heredoc - -spec :: Spec -spec = around (withApp cfgDefault) $ do +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = around (withApp cfgDefault struct pool) $ do describe "Querying a table with a column called count" $ it "should not confuse count column with pg_catalog.count aggregate" $ @@ -219,6 +222,11 @@ spec = around (withApp cfgDefault) $ do get "/users_tasks?user_id=eq.2&task_id=eq.6&select=*, comments{content}" `shouldRespondWith` "[{\"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"}}]}]|] + + describe "Plurality singular" $ do it "will select an existing object" $ request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] "" diff --git a/test/Feature/RangeSpec.hs b/test/Feature/RangeSpec.hs index 0e7d519f6..a8e276272 100644 --- a/test/Feature/RangeSpec.hs +++ b/test/Feature/RangeSpec.hs @@ -6,11 +6,15 @@ import Test.Hspec.Wai.JSON import Network.HTTP.Types import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus)) -import SpecHelper +import Hasql as H +import Hasql.Postgres as P -spec :: Spec -spec = beforeAll resetDb - . around (withApp cfgDefault) $ +import SpecHelper +import PostgREST.Types (DbStructure(..)) + +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = beforeAll resetDb + . around (withApp cfgDefault struct pool) $ describe "GET /items" $ do context "without range headers" $ do diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index 4a6d9f0dd..034081cb1 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -4,12 +4,16 @@ import Test.Hspec hiding (pendingWith) import Test.Hspec.Wai import Test.Hspec.Wai.JSON +import Hasql as H +import Hasql.Postgres as P + import SpecHelper +import PostgREST.Types (DbStructure(..)) import Network.HTTP.Types -spec :: Spec -spec = around (withApp cfgDefault) $ do +spec :: DbStructure -> H.Pool P.Postgres -> Spec +spec struct pool = around (withApp cfgDefault struct pool) $ do describe "GET /" $ do it "lists views in schema" $ request methodGet "/" [] "" @@ -208,16 +212,14 @@ spec = around (withApp cfgDefault) $ do } |] - it "includes foreign key data" $ do - pendingWith "have to resolve issue #107" - + it "includes foreign key data" $ request methodOptions "/has_fk" [] "" `shouldRespondWith` [json| { "pkey": ["id"], "columns":[ { - "default": "nextval('\"1\".has_fk_id_seq'::regclass)", + "default": "nextval('test.has_fk_id_seq'::regclass)", "precision": 64, "updatable": true, "schema": "test", @@ -239,7 +241,7 @@ spec = around (withApp cfgDefault) $ do "nullable": true, "position": 2, "enum": [], - "references": {"table": "auto_incrementing_pk", "column": "id"} + "references": {"schema":"test", "table": "auto_incrementing_pk", "column": "id"} }, { "default": null, "precision": null, @@ -251,14 +253,13 @@ spec = around (withApp cfgDefault) $ do "nullable": true, "position": 3, "enum": [], - "references": {"table": "simple_pk", "column": "k"} + "references": {"schema":"test", "table": "simple_pk", "column": "k"} } ] } |] - it "includes all information on views for renamed columns, and raises relations to correct schema" $ do - pendingWith "have to resolve issue #107" + it "includes all information on views for renamed columns, and raises relations to correct schema" $ request methodOptions "/articleStars" [] "" `shouldRespondWith` [json| { diff --git a/test/Main.hs b/test/Main.hs index 2a8006727..81d483a62 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -2,7 +2,36 @@ module Main where import Test.Hspec import SpecHelper -import Spec + +--import PostgREST.Types (DbStructure(..)) + +import qualified Feature.AuthSpec +import qualified Feature.CorsSpec +import qualified Feature.DeleteSpec +import qualified Feature.InsertSpec +import qualified Feature.QueryLimitedSpec +import qualified Feature.QuerySpec +import qualified Feature.RangeSpec +import qualified Feature.StructureSpec main :: IO () -main = setupDb >> hspec spec +main = do + setupDb + + pool <- specDbPool + dbStructure <- specDbStructure pool + + -- Not using hspec-discover because we want to precompute + -- the db structure and pass it to specs for speed + hspec $ specs dbStructure pool + + where + specs dbStructure pool = do + describe "Feature.AuthSpec" $ Feature.AuthSpec.spec dbStructure pool + describe "Feature.CorsSpec" $ Feature.CorsSpec.spec dbStructure pool + describe "Feature.DeleteSpec" $ Feature.DeleteSpec.spec dbStructure pool + describe "Feature.InsertSpec" $ Feature.InsertSpec.spec dbStructure pool + describe "Feature.QueryLimitedSpec" $ Feature.QueryLimitedSpec.spec dbStructure pool + describe "Feature.QuerySpec" $ Feature.QuerySpec.spec dbStructure pool + describe "Feature.RangeSpec" $ Feature.RangeSpec.spec dbStructure pool + describe "Feature.StructureSpec" $ Feature.StructureSpec.spec dbStructure pool diff --git a/test/Spec.hs b/test/Spec.hs deleted file mode 100644 index b4e92e756..000000000 --- a/test/Spec.hs +++ /dev/null @@ -1 +0,0 @@ -{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-} diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index a63340145..ea0de9db5 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -30,6 +30,7 @@ import PostgREST.Config (AppConfig(..)) import PostgREST.Middleware import PostgREST.Error(pgErrResponse) import PostgREST.DbStructure +import PostgREST.Types dbString :: String dbString = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_test" @@ -49,20 +50,23 @@ testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30 pgSettings :: P.Settings pgSettings = P.StringSettings $ cs dbString -withApp :: AppConfig -> ActionWith Application -> IO () -withApp config perform = do - pool :: H.Pool P.Postgres - <- H.acquirePool pgSettings testPoolOpts +specDbPool :: IO (H.Pool P.Postgres) +specDbPool = H.acquirePool pgSettings testPoolOpts - let txSettings = Just (H.ReadCommitted, Just True) - dbOrError <- H.session pool $ H.tx txSettings $ getDbStructure (cs $ configSchema config) - db <- either (fail . show) return dbOrError +specDbStructure :: H.Pool P.Postgres -> IO DbStructure +specDbStructure pool = do + dbOrError <- H.session pool $ H.tx specTxSettings + $ getDbStructure "test" + either (fail . show) return dbOrError +withApp :: AppConfig -> DbStructure -> H.Pool P.Postgres + -> ActionWith Application -> IO () +withApp config dbStructure pool perform = do perform $ middle $ \req resp -> do time <- getPOSIXTime body <- strictRequestBody req - result <- liftIO $ H.session pool $ H.tx txSettings - $ runWithClaims config time (app db config body) req + result <- liftIO $ H.session pool $ H.tx specTxSettings + $ runWithClaims config time (app dbStructure config body) req either (resp . pgErrResponse) resp result where middle = defaultMiddle @@ -111,3 +115,6 @@ clearTable table = do pool <- testPool void . liftIO $ H.session pool $ H.tx Nothing $ H.unitEx $ B.Stmt ("truncate table test." <> table <> " cascade") V.empty True + +specTxSettings :: Maybe (TxIsolationLevel, Maybe Bool) +specTxSettings = Just (H.ReadCommitted, Just True)