Merge branch 'spec-cache-dbstructure'
This commit is contained in:
@@ -158,7 +158,6 @@ Test-Suite spec
|
|||||||
, PostgREST.RangeQuery
|
, PostgREST.RangeQuery
|
||||||
, PostgREST.ApiRequest
|
, PostgREST.ApiRequest
|
||||||
, PostgREST.Types
|
, PostgREST.Types
|
||||||
, Spec
|
|
||||||
, SpecHelper
|
, SpecHelper
|
||||||
, TestTypes
|
, TestTypes
|
||||||
Build-Depends: aeson
|
Build-Depends: aeson
|
||||||
|
|||||||
+194
-14
@@ -202,6 +202,98 @@ allColumns tabs = do
|
|||||||
info.column_default AS default_value,
|
info.column_default AS default_value,
|
||||||
array_to_string(enum_info.vals, ',') AS enum
|
array_to_string(enum_info.vals, ',') AS enum
|
||||||
FROM (
|
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
|
SELECT
|
||||||
table_schema,
|
table_schema,
|
||||||
table_name,
|
table_name,
|
||||||
@@ -214,7 +306,8 @@ allColumns tabs = do
|
|||||||
numeric_precision,
|
numeric_precision,
|
||||||
column_default,
|
column_default,
|
||||||
udt_name
|
udt_name
|
||||||
FROM information_schema.columns
|
/*-- FROM information_schema.columns*/
|
||||||
|
FROM columns
|
||||||
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
||||||
) AS info
|
) AS info
|
||||||
LEFT OUTER JOIN (
|
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 :: [Table] -> H.Tx P.Postgres s [PrimaryKey]
|
||||||
allPrimaryKeys tabs = do
|
allPrimaryKeys tabs = do
|
||||||
pks <- H.listEx $ [H.stmt|
|
pks <- H.listEx $ [H.stmt|
|
||||||
SELECT
|
/*
|
||||||
kc.table_schema,
|
-- CTE to replace information_schema.table_constraints to remove owner limit
|
||||||
kc.table_name,
|
*/
|
||||||
kc.column_name
|
WITH tc AS (
|
||||||
FROM
|
SELECT current_database()::information_schema.sql_identifier AS constraint_catalog,
|
||||||
information_schema.table_constraints tc,
|
nc.nspname::information_schema.sql_identifier AS constraint_schema,
|
||||||
information_schema.key_column_usage kc
|
c.conname::information_schema.sql_identifier AS constraint_name,
|
||||||
WHERE
|
current_database()::information_schema.sql_identifier AS table_catalog,
|
||||||
tc.constraint_type = 'PRIMARY KEY' AND
|
nr.nspname::information_schema.sql_identifier AS table_schema,
|
||||||
kc.table_name = tc.table_name AND
|
r.relname::information_schema.sql_identifier AS table_name,
|
||||||
kc.table_schema = tc.table_schema AND
|
CASE c.contype
|
||||||
kc.constraint_name = tc.constraint_name AND
|
WHEN 'c'::"char" THEN 'CHECK'::text
|
||||||
kc.table_schema NOT IN ('pg_catalog', 'information_schema')
|
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
|
return $ mapMaybe (pkFromRow tabs) pks
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import Test.Hspec.Wai
|
|||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
spec :: Spec
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec = around (withApp cfgDefault)
|
spec struct pool = around (withApp cfgDefault struct pool)
|
||||||
$ describe "authorization" $ do
|
$ describe "authorization" $ do
|
||||||
|
|
||||||
it "hides tables that anonymous does not own" $
|
it "hides tables that anonymous does not own" $
|
||||||
|
|||||||
@@ -6,13 +6,17 @@ import Test.Hspec.Wai
|
|||||||
import Network.Wai.Test (SResponse(simpleHeaders, simpleBody))
|
import Network.Wai.Test (SResponse(simpleHeaders, simpleBody))
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
-- }}}
|
-- }}}
|
||||||
|
|
||||||
spec :: Spec
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec = around (withApp cfgDefault) $ describe "CORS" $ do
|
spec struct pool = around (withApp cfgDefault struct pool) $ describe "CORS" $ do
|
||||||
let preflightHeaders = [
|
let preflightHeaders = [
|
||||||
("Accept", "*/*"),
|
("Accept", "*/*"),
|
||||||
("Origin", "http://example.com"),
|
("Origin", "http://example.com"),
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ module Feature.DeleteSpec where
|
|||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
|
|
||||||
spec :: Spec
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec = beforeAll resetDb
|
spec struct pool = beforeAll resetDb
|
||||||
. around (withApp cfgDefault) $
|
. around (withApp cfgDefault struct pool) $
|
||||||
describe "Deleting" $ do
|
describe "Deleting" $ do
|
||||||
context "existing record" $ do
|
context "existing record" $ do
|
||||||
it "succeeds with 204 and deletion count" $
|
it "succeeds with 204 and deletion count" $
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import Test.Hspec.Wai
|
|||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
import Network.Wai.Test (SResponse(simpleBody,simpleHeaders,simpleStatus))
|
import Network.Wai.Test (SResponse(simpleBody,simpleHeaders,simpleStatus))
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
@@ -16,8 +20,8 @@ import Control.Monad (replicateM_)
|
|||||||
|
|
||||||
import TestTypes(IncPK(..), CompoundPK(..))
|
import TestTypes(IncPK(..), CompoundPK(..))
|
||||||
|
|
||||||
spec :: Spec
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec = beforeAll_ resetDb $ around (withApp cfgDefault) $ do
|
spec struct pool = beforeAll_ resetDb $ around (withApp cfgDefault struct pool) $ do
|
||||||
describe "Posting new record" $ do
|
describe "Posting new record" $ do
|
||||||
after_ (clearTable "menagerie") . context "disparate csv types" $ do
|
after_ (clearTable "menagerie") . context "disparate csv types" $ do
|
||||||
it "accepts disparate json types" $ do
|
it "accepts disparate json types" $ do
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ import Test.Hspec.Wai.JSON
|
|||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.Wai.Test (SResponse(simpleHeaders, simpleStatus))
|
import Network.Wai.Test (SResponse(simpleHeaders, simpleStatus))
|
||||||
|
|
||||||
import SpecHelper
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
spec :: Spec
|
import SpecHelper
|
||||||
spec =
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
|
||||||
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
|
spec struct pool =
|
||||||
beforeAll resetDb
|
beforeAll resetDb
|
||||||
. around (withApp $ cfgLimitRows 3) $
|
. around (withApp (cfgLimitRows 3) struct pool) $
|
||||||
describe "Requesting many items with server limits enabled" $ do
|
describe "Requesting many items with server limits enabled" $ do
|
||||||
it "restricts results" $
|
it "restricts results" $
|
||||||
get "/items"
|
get "/items"
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ import Test.Hspec.Wai.JSON
|
|||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.Wai.Test (SResponse(simpleHeaders))
|
import Network.Wai.Test (SResponse(simpleHeaders))
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
import Text.Heredoc
|
import Text.Heredoc
|
||||||
|
|
||||||
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec :: Spec
|
spec struct pool = around (withApp cfgDefault struct pool) $ do
|
||||||
spec = around (withApp cfgDefault) $ do
|
|
||||||
|
|
||||||
describe "Querying a table with a column called count" $
|
describe "Querying a table with a column called count" $
|
||||||
it "should not confuse count column with pg_catalog.count aggregate" $
|
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`
|
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\"}]}]"
|
"[{\"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
|
describe "Plurality singular" $ do
|
||||||
it "will select an existing object" $
|
it "will select an existing object" $
|
||||||
request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] ""
|
request methodGet "/items?id=eq.5" [("Prefer","plurality=singular")] ""
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ import Test.Hspec.Wai.JSON
|
|||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus))
|
import Network.Wai.Test (SResponse(simpleHeaders,simpleStatus))
|
||||||
|
|
||||||
import SpecHelper
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
spec :: Spec
|
import SpecHelper
|
||||||
spec = beforeAll resetDb
|
import PostgREST.Types (DbStructure(..))
|
||||||
. around (withApp cfgDefault) $
|
|
||||||
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
|
spec struct pool = beforeAll resetDb
|
||||||
|
. around (withApp cfgDefault struct pool) $
|
||||||
describe "GET /items" $ do
|
describe "GET /items" $ do
|
||||||
|
|
||||||
context "without range headers" $ do
|
context "without range headers" $ do
|
||||||
|
|||||||
@@ -4,12 +4,16 @@ import Test.Hspec hiding (pendingWith)
|
|||||||
import Test.Hspec.Wai
|
import Test.Hspec.Wai
|
||||||
import Test.Hspec.Wai.JSON
|
import Test.Hspec.Wai.JSON
|
||||||
|
|
||||||
|
import Hasql as H
|
||||||
|
import Hasql.Postgres as P
|
||||||
|
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
|
|
||||||
spec :: Spec
|
spec :: DbStructure -> H.Pool P.Postgres -> Spec
|
||||||
spec = around (withApp cfgDefault) $ do
|
spec struct pool = around (withApp cfgDefault struct pool) $ do
|
||||||
describe "GET /" $ do
|
describe "GET /" $ do
|
||||||
it "lists views in schema" $
|
it "lists views in schema" $
|
||||||
request methodGet "/" [] ""
|
request methodGet "/" [] ""
|
||||||
@@ -208,16 +212,14 @@ spec = around (withApp cfgDefault) $ do
|
|||||||
}
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
it "includes foreign key data" $ do
|
it "includes foreign key data" $
|
||||||
pendingWith "have to resolve issue #107"
|
|
||||||
|
|
||||||
request methodOptions "/has_fk" [] ""
|
request methodOptions "/has_fk" [] ""
|
||||||
`shouldRespondWith` [json|
|
`shouldRespondWith` [json|
|
||||||
{
|
{
|
||||||
"pkey": ["id"],
|
"pkey": ["id"],
|
||||||
"columns":[
|
"columns":[
|
||||||
{
|
{
|
||||||
"default": "nextval('\"1\".has_fk_id_seq'::regclass)",
|
"default": "nextval('test.has_fk_id_seq'::regclass)",
|
||||||
"precision": 64,
|
"precision": 64,
|
||||||
"updatable": true,
|
"updatable": true,
|
||||||
"schema": "test",
|
"schema": "test",
|
||||||
@@ -239,7 +241,7 @@ spec = around (withApp cfgDefault) $ do
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"position": 2,
|
"position": 2,
|
||||||
"enum": [],
|
"enum": [],
|
||||||
"references": {"table": "auto_incrementing_pk", "column": "id"}
|
"references": {"schema":"test", "table": "auto_incrementing_pk", "column": "id"}
|
||||||
}, {
|
}, {
|
||||||
"default": null,
|
"default": null,
|
||||||
"precision": null,
|
"precision": null,
|
||||||
@@ -251,14 +253,13 @@ spec = around (withApp cfgDefault) $ do
|
|||||||
"nullable": true,
|
"nullable": true,
|
||||||
"position": 3,
|
"position": 3,
|
||||||
"enum": [],
|
"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
|
it "includes all information on views for renamed columns, and raises relations to correct schema" $
|
||||||
pendingWith "have to resolve issue #107"
|
|
||||||
request methodOptions "/articleStars" [] ""
|
request methodOptions "/articleStars" [] ""
|
||||||
`shouldRespondWith` [json|
|
`shouldRespondWith` [json|
|
||||||
{
|
{
|
||||||
|
|||||||
+31
-2
@@ -2,7 +2,36 @@ module Main where
|
|||||||
|
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
import SpecHelper
|
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 :: 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
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-}
|
|
||||||
+16
-9
@@ -30,6 +30,7 @@ import PostgREST.Config (AppConfig(..))
|
|||||||
import PostgREST.Middleware
|
import PostgREST.Middleware
|
||||||
import PostgREST.Error(pgErrResponse)
|
import PostgREST.Error(pgErrResponse)
|
||||||
import PostgREST.DbStructure
|
import PostgREST.DbStructure
|
||||||
|
import PostgREST.Types
|
||||||
|
|
||||||
dbString :: String
|
dbString :: String
|
||||||
dbString = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_test"
|
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.Settings
|
||||||
pgSettings = P.StringSettings $ cs dbString
|
pgSettings = P.StringSettings $ cs dbString
|
||||||
|
|
||||||
withApp :: AppConfig -> ActionWith Application -> IO ()
|
specDbPool :: IO (H.Pool P.Postgres)
|
||||||
withApp config perform = do
|
specDbPool = H.acquirePool pgSettings testPoolOpts
|
||||||
pool :: H.Pool P.Postgres
|
|
||||||
<- H.acquirePool pgSettings testPoolOpts
|
|
||||||
|
|
||||||
let txSettings = Just (H.ReadCommitted, Just True)
|
specDbStructure :: H.Pool P.Postgres -> IO DbStructure
|
||||||
dbOrError <- H.session pool $ H.tx txSettings $ getDbStructure (cs $ configSchema config)
|
specDbStructure pool = do
|
||||||
db <- either (fail . show) return dbOrError
|
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
|
perform $ middle $ \req resp -> do
|
||||||
time <- getPOSIXTime
|
time <- getPOSIXTime
|
||||||
body <- strictRequestBody req
|
body <- strictRequestBody req
|
||||||
result <- liftIO $ H.session pool $ H.tx txSettings
|
result <- liftIO $ H.session pool $ H.tx specTxSettings
|
||||||
$ runWithClaims config time (app db config body) req
|
$ runWithClaims config time (app dbStructure config body) req
|
||||||
either (resp . pgErrResponse) resp result
|
either (resp . pgErrResponse) resp result
|
||||||
|
|
||||||
where middle = defaultMiddle
|
where middle = defaultMiddle
|
||||||
@@ -111,3 +115,6 @@ clearTable table = do
|
|||||||
pool <- testPool
|
pool <- testPool
|
||||||
void . liftIO $ H.session pool $ H.tx Nothing $
|
void . liftIO $ H.session pool $ H.tx Nothing $
|
||||||
H.unitEx $ B.Stmt ("truncate table test." <> table <> " cascade") V.empty True
|
H.unitEx $ B.Stmt ("truncate table test." <> table <> " cascade") V.empty True
|
||||||
|
|
||||||
|
specTxSettings :: Maybe (TxIsolationLevel, Maybe Bool)
|
||||||
|
specTxSettings = Just (H.ReadCommitted, Just True)
|
||||||
|
|||||||
Reference in New Issue
Block a user