diff --git a/postgrest.cabal b/postgrest.cabal
index 9559b7aff..0c546fe30 100644
--- a/postgrest.cabal
+++ b/postgrest.cabal
@@ -158,6 +158,8 @@ test-suite spec
Feature.JsonOperatorSpec
Feature.NoJwtSpec
Feature.NonexistentSchemaSpec
+ Feature.OpenApiSpec
+ Feature.OptionsSpec
Feature.ProxySpec
Feature.QueryLimitedSpec
Feature.QuerySpec
@@ -166,7 +168,6 @@ test-suite spec
Feature.RpcPreRequestGucsSpec
Feature.RpcSpec
Feature.SingularSpec
- Feature.StructureSpec
Feature.UnicodeSpec
Feature.UpdateSpec
Feature.UpsertSpec
diff --git a/test/Feature/OpenApiSpec.hs b/test/Feature/OpenApiSpec.hs
new file mode 100644
index 000000000..a5cf31070
--- /dev/null
+++ b/test/Feature/OpenApiSpec.hs
@@ -0,0 +1,504 @@
+module Feature.OpenApiSpec where
+
+import Control.Lens ((^?))
+import Data.Aeson.Types (Value (..))
+import Network.Wai (Application)
+import Network.Wai.Test (SResponse (..))
+
+import Data.Aeson.Lens
+import Data.Aeson.QQ
+import Network.HTTP.Types
+import Test.Hspec hiding (pendingWith)
+import Test.Hspec.Wai
+
+import PostgREST.Config (docsVersion)
+import Protolude hiding (get)
+import SpecHelper
+
+spec :: SpecWith ((), Application)
+spec = describe "OpenAPI" $ do
+ it "root path returns a valid openapi spec" $ do
+ validateOpenApiResponse [("Accept", "application/openapi+json")]
+ request methodHead "/" (acceptHdrs "application/openapi+json") ""
+ `shouldRespondWith` "" { matchStatus = 200 }
+
+ it "should respond to openapi request on none root path with 415" $
+ request methodGet "/items"
+ (acceptHdrs "application/openapi+json") ""
+ `shouldRespondWith` 415
+
+ it "includes postgrest.org current version api docs" $ do
+ r <- simpleBody <$> get "/"
+
+ let docsUrl = r ^? key "externalDocs" . key "url"
+
+ liftIO $ docsUrl `shouldBe` Just (String ("https://postgrest.org/en/" <> docsVersion <> "/api.html"))
+
+ describe "table" $ do
+
+ it "includes paths to tables" $ do
+ r <- simpleBody <$> get "/"
+
+ let method s = key "paths" . key "/child_entities" . key s
+ childGetSummary = r ^? method "get" . key "summary"
+ childGetDescription = r ^? method "get" . key "description"
+ getParameters = r ^? method "get" . key "parameters"
+ postParameters = r ^? method "post" . key "parameters"
+ postResponse = r ^? method "post" . key "responses" . key "201" . key "description"
+ patchResponse = r ^? method "patch" . key "responses" . key "204" . key "description"
+ deleteResponse = r ^? method "delete" . key "responses" . key "204" . key "description"
+
+ let grandChildGet s = key "paths" . key "/grandchild_entities" . key "get" . key s
+ grandChildGetSummary = r ^? grandChildGet "summary"
+ grandChildGetDescription = r ^? grandChildGet "description"
+
+ liftIO $ do
+
+ childGetSummary `shouldBe` Just "child_entities comment"
+
+ childGetDescription `shouldBe` Nothing
+
+ grandChildGetSummary `shouldBe` Just "grandchild_entities summary"
+
+ grandChildGetDescription `shouldBe` Just "grandchild_entities description\nthat spans\nmultiple lines"
+
+ getParameters `shouldBe` Just
+ [aesonQQ|
+ [
+ { "$ref": "#/parameters/rowFilter.child_entities.id" },
+ { "$ref": "#/parameters/rowFilter.child_entities.name" },
+ { "$ref": "#/parameters/rowFilter.child_entities.parent_id" },
+ { "$ref": "#/parameters/select" },
+ { "$ref": "#/parameters/order" },
+ { "$ref": "#/parameters/range" },
+ { "$ref": "#/parameters/rangeUnit" },
+ { "$ref": "#/parameters/offset" },
+ { "$ref": "#/parameters/limit" },
+ { "$ref": "#/parameters/preferCount" }
+ ]
+ |]
+
+ postParameters `shouldBe` Just
+ [aesonQQ|
+ [
+ { "$ref": "#/parameters/body.child_entities" },
+ { "$ref": "#/parameters/select" },
+ { "$ref": "#/parameters/preferReturn" }
+ ]
+ |]
+
+ postResponse `shouldBe` Just "Created"
+
+ patchResponse `shouldBe` Just "No Content"
+
+ deleteResponse `shouldBe` Just "No Content"
+
+ it "includes an array type for GET responses" $ do
+ r <- simpleBody <$> get "/"
+
+ let childGetSchema = r ^? key "paths"
+ . key "/child_entities"
+ . key "get"
+ . key "responses"
+ . key "200"
+ . key "schema"
+
+ liftIO $
+ childGetSchema `shouldBe` Just
+ [aesonQQ|
+ {
+ "items": {
+ "$ref": "#/definitions/child_entities"
+ },
+ "type": "array"
+ }
+ |]
+
+ it "includes definitions to tables" $ do
+ r <- simpleBody <$> get "/"
+
+ let def = r ^? key "definitions" . key "child_entities"
+
+ liftIO $
+
+ def `shouldBe` Just
+ [aesonQQ|
+ {
+ "type": "object",
+ "description": "child_entities comment",
+ "properties": {
+ "id": {
+ "description": "child_entities id comment\n\nNote:\nThis is a Primary Key.",
+ "format": "integer",
+ "type": "integer"
+ },
+ "name": {
+ "description": "child_entities name comment. Can be longer than sixty-three characters long",
+ "format": "text",
+ "type": "string"
+ },
+ "parent_id": {
+ "description": "Note:\nThis is a Foreign Key to `entities.id`.",
+ "format": "integer",
+ "type": "integer"
+ }
+ },
+ "required": [
+ "id"
+ ]
+ }
+ |]
+
+ it "doesn't include privileged table for anonymous" $ do
+ r <- simpleBody <$> get "/"
+ let tablePath = r ^? key "paths" . key "/authors_only"
+
+ liftIO $ tablePath `shouldBe` Nothing
+
+ it "includes table if user has permission" $ do
+ let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
+ r <- simpleBody <$> request methodGet "/" [auth] ""
+ let tableTag = r ^? key "paths" . key "/authors_only"
+ . key "post" . key "tags"
+ . nth 0
+ liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|]
+
+ describe "Foreign table" $
+
+ it "includes foreign table properties" $ do
+ r <- simpleBody <$> get "/"
+
+ let method s = key "paths" . key "/projects_dump" . key s
+ getSummary = r ^? method "get" . key "summary"
+ getDescription = r ^? method "get" . key "description"
+ getParameters = r ^? method "get" . key "parameters"
+
+ liftIO $ do
+
+ getSummary `shouldBe` Just "A temporary projects dump"
+
+ getDescription `shouldBe` Just "Just a test for foreign tables"
+
+ getParameters `shouldBe` Just
+ [aesonQQ|
+ [
+ { "$ref": "#/parameters/rowFilter.projects_dump.id" },
+ { "$ref": "#/parameters/rowFilter.projects_dump.name" },
+ { "$ref": "#/parameters/rowFilter.projects_dump.client_id" },
+ { "$ref": "#/parameters/select" },
+ { "$ref": "#/parameters/order" },
+ { "$ref": "#/parameters/range" },
+ { "$ref": "#/parameters/rangeUnit" },
+ { "$ref": "#/parameters/offset" },
+ { "$ref": "#/parameters/limit" },
+ { "$ref": "#/parameters/preferCount" }
+ ]
+ |]
+
+ describe "Materialized view" $
+
+ it "includes materialized view properties" $ do
+ r <- simpleBody <$> get "/"
+
+ let method s = key "paths" . key "/materialized_projects" . key s
+ summary = r ^? method "get" . key "summary"
+ description = r ^? method "get" . key "description"
+ parameters = r ^? method "get" . key "parameters"
+
+ liftIO $ do
+
+ summary `shouldBe` Just "A materialized view for projects"
+
+ description `shouldBe` Just "Just a test for materialized views"
+
+ parameters `shouldBe` Just
+ [aesonQQ|
+ [
+ { "$ref": "#/parameters/rowFilter.materialized_projects.id" },
+ { "$ref": "#/parameters/rowFilter.materialized_projects.name" },
+ { "$ref": "#/parameters/rowFilter.materialized_projects.client_id" },
+ { "$ref": "#/parameters/select" },
+ { "$ref": "#/parameters/order" },
+ { "$ref": "#/parameters/range" },
+ { "$ref": "#/parameters/rangeUnit" },
+ { "$ref": "#/parameters/offset" },
+ { "$ref": "#/parameters/limit" },
+ { "$ref": "#/parameters/preferCount" }
+ ]
+ |]
+
+ describe "VIEW that has a source FK based on a UNIQUE key" $
+
+ it "includes fk description" $ do
+ r <- simpleBody <$> get "/"
+
+ let referralLink = r ^? key "definitions" . key "referrals" . key "properties" . key "link"
+
+ liftIO $
+ referralLink `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "integer",
+ "type": "integer",
+ "description": "Note:\nThis is a Foreign Key to `pages.link`."
+ }
+ |]
+
+ describe "PostgreSQL to Swagger Type Mapping" $ do
+
+ it "character varying to string" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_character_varying"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "character varying",
+ "type": "string"
+ }
+ |]
+ it "character(1) to string" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_character"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "maxLength": 1,
+ "format": "character",
+ "type": "string"
+ }
+ |]
+
+ it "text to string" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_text"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "text",
+ "type": "string"
+ }
+ |]
+
+ it "boolean to boolean" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_boolean"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "boolean",
+ "type": "boolean"
+ }
+ |]
+
+ it "smallint to integer" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_smallint"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "smallint",
+ "type": "integer"
+ }
+ |]
+
+ it "integer to integer" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_integer"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "integer",
+ "type": "integer"
+ }
+ |]
+
+ it "bigint to integer" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_bigint"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "bigint",
+ "type": "integer"
+ }
+ |]
+
+ it "numeric to number" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_numeric"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "numeric",
+ "type": "number"
+ }
+ |]
+
+ it "real to number" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_real"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "real",
+ "type": "number"
+ }
+ |]
+
+ it "double_precision to number" $ do
+ r <- simpleBody <$> get "/"
+
+ let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_double_precision"
+
+ liftIO $
+
+ types `shouldBe` Just
+ [aesonQQ|
+ {
+ "format": "double precision",
+ "type": "number"
+ }
+ |]
+
+ describe "RPC" $ do
+
+ it "includes function summary/description and body schema for arguments" $ do
+ r <- simpleBody <$> get "/"
+
+ let method s = key "paths" . key "/rpc/varied_arguments" . key s
+ args = r ^? method "post" . key "parameters" . nth 0 . key "schema"
+ summary = r ^? method "post" . key "summary"
+ description = r ^? method "post" . key "description"
+
+ liftIO $ do
+
+ summary `shouldBe` Just "An RPC function"
+
+ description `shouldBe` Just "Just a test for RPC function arguments"
+
+ args `shouldBe` Just
+ [aesonQQ|
+ {
+ "required": [
+ "double",
+ "varchar",
+ "boolean",
+ "date",
+ "money",
+ "enum",
+ "arr"
+ ],
+ "properties": {
+ "double": {
+ "format": "double precision",
+ "type": "number"
+ },
+ "varchar": {
+ "format": "character varying",
+ "type": "string"
+ },
+ "boolean": {
+ "format": "boolean",
+ "type": "boolean"
+ },
+ "date": {
+ "format": "date",
+ "type": "string"
+ },
+ "money": {
+ "format": "money",
+ "type": "string"
+ },
+ "enum": {
+ "format": "enum_menagerie_type",
+ "type": "string"
+ },
+ "arr": {
+ "format": "text[]",
+ "type": "string"
+ },
+ "integer": {
+ "format": "integer",
+ "type": "integer"
+ },
+ "json": {
+ "format": "json",
+ "type": "string"
+ },
+ "jsonb": {
+ "format": "jsonb",
+ "type": "string"
+ }
+ },
+ "type": "object",
+ "description": "An RPC function\n\nJust a test for RPC function arguments"
+ }
+ |]
+
+ it "doesn't include privileged function for anonymous" $ do
+ r <- simpleBody <$> get "/"
+ let funcPath = r ^? key "paths" . key "/rpc/privileged_hello"
+
+ liftIO $ funcPath `shouldBe` Nothing
+
+ it "includes function if user has permission" $ do
+ let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
+ r <- simpleBody <$> request methodGet "/" [auth] ""
+ let funcTag = r ^? key "paths" . key "/rpc/privileged_hello"
+ . key "post" . key "tags"
+ . nth 0
+
+ liftIO $ funcTag `shouldBe` Just [aesonQQ|"(rpc) privileged_hello"|]
+
+ it "doesn't include OUT params of function as required parameters" $ do
+ r <- simpleBody <$> get "/"
+ let params = r ^? key "paths" . key "/rpc/many_out_params"
+ . key "post" . key "parameters" . nth 0
+ . key "schema". key "required"
+
+ liftIO $ params `shouldBe` Nothing
+
+ it "includes INOUT params(with no DEFAULT) of function as required parameters" $ do
+ r <- simpleBody <$> get "/"
+ let params = r ^? key "paths" . key "/rpc/many_inout_params"
+ . key "post" . key "parameters" . nth 0
+ . key "schema". key "required"
+
+ liftIO $ params `shouldBe` Just [aesonQQ|["num", "str"]|]
+
diff --git a/test/Feature/OptionsSpec.hs b/test/Feature/OptionsSpec.hs
new file mode 100644
index 000000000..b4dd89d64
--- /dev/null
+++ b/test/Feature/OptionsSpec.hs
@@ -0,0 +1,26 @@
+module Feature.OptionsSpec where
+
+import Network.Wai (Application)
+import Network.Wai.Test (SResponse (..))
+
+import Network.HTTP.Types
+import Test.Hspec
+import Test.Hspec.Wai
+
+import Protolude
+import SpecHelper
+
+spec :: SpecWith ((), Application)
+spec = describe "Allow header" $ do
+ it "includes read/write verbs for writeable table" $ do
+ r <- request methodOptions "/items" [] ""
+ liftIO $
+ simpleHeaders r `shouldSatisfy`
+ matchHeader "Allow" "GET,POST,PATCH,DELETE"
+
+ it "includes read verbs for read-only table" $ do
+ r <- request methodOptions "/has_count_column" [] ""
+ liftIO $
+ simpleHeaders r `shouldSatisfy`
+ matchHeader "Allow" "GET"
+
diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs
deleted file mode 100644
index c288edb8f..000000000
--- a/test/Feature/StructureSpec.hs
+++ /dev/null
@@ -1,521 +0,0 @@
-module Feature.StructureSpec where
-
-import Control.Lens ((^?))
-import Data.Aeson.Types (Value (..))
-import Network.Wai (Application)
-import Network.Wai.Test (SResponse (..))
-
-import Data.Aeson.Lens
-import Data.Aeson.QQ
-import Network.HTTP.Types
-import Test.Hspec hiding (pendingWith)
-import Test.Hspec.Wai
-
-
-
-import PostgREST.Config (docsVersion)
-import Protolude hiding (get)
-import SpecHelper
-
-spec :: SpecWith ((), Application)
-spec = do
-
- describe "OpenAPI" $ do
- it "root path returns a valid openapi spec" $ do
- validateOpenApiResponse [("Accept", "application/openapi+json")]
- request methodHead "/" (acceptHdrs "application/openapi+json") ""
- `shouldRespondWith` "" { matchStatus = 200 }
-
- it "should respond to openapi request on none root path with 415" $
- request methodGet "/items"
- (acceptHdrs "application/openapi+json") ""
- `shouldRespondWith` 415
-
- it "includes postgrest.org current version api docs" $ do
- r <- simpleBody <$> get "/"
-
- let docsUrl = r ^? key "externalDocs" . key "url"
-
- liftIO $ docsUrl `shouldBe` Just (String ("https://postgrest.org/en/" <> docsVersion <> "/api.html"))
-
- describe "table" $ do
-
- it "includes paths to tables" $ do
- r <- simpleBody <$> get "/"
-
- let method s = key "paths" . key "/child_entities" . key s
- childGetSummary = r ^? method "get" . key "summary"
- childGetDescription = r ^? method "get" . key "description"
- getParameters = r ^? method "get" . key "parameters"
- postParameters = r ^? method "post" . key "parameters"
- postResponse = r ^? method "post" . key "responses" . key "201" . key "description"
- patchResponse = r ^? method "patch" . key "responses" . key "204" . key "description"
- deleteResponse = r ^? method "delete" . key "responses" . key "204" . key "description"
-
- let grandChildGet s = key "paths" . key "/grandchild_entities" . key "get" . key s
- grandChildGetSummary = r ^? grandChildGet "summary"
- grandChildGetDescription = r ^? grandChildGet "description"
-
- liftIO $ do
-
- childGetSummary `shouldBe` Just "child_entities comment"
-
- childGetDescription `shouldBe` Nothing
-
- grandChildGetSummary `shouldBe` Just "grandchild_entities summary"
-
- grandChildGetDescription `shouldBe` Just "grandchild_entities description\nthat spans\nmultiple lines"
-
- getParameters `shouldBe` Just
- [aesonQQ|
- [
- { "$ref": "#/parameters/rowFilter.child_entities.id" },
- { "$ref": "#/parameters/rowFilter.child_entities.name" },
- { "$ref": "#/parameters/rowFilter.child_entities.parent_id" },
- { "$ref": "#/parameters/select" },
- { "$ref": "#/parameters/order" },
- { "$ref": "#/parameters/range" },
- { "$ref": "#/parameters/rangeUnit" },
- { "$ref": "#/parameters/offset" },
- { "$ref": "#/parameters/limit" },
- { "$ref": "#/parameters/preferCount" }
- ]
- |]
-
- postParameters `shouldBe` Just
- [aesonQQ|
- [
- { "$ref": "#/parameters/body.child_entities" },
- { "$ref": "#/parameters/select" },
- { "$ref": "#/parameters/preferReturn" }
- ]
- |]
-
- postResponse `shouldBe` Just "Created"
-
- patchResponse `shouldBe` Just "No Content"
-
- deleteResponse `shouldBe` Just "No Content"
-
- it "includes an array type for GET responses" $ do
- r <- simpleBody <$> get "/"
-
- let childGetSchema = r ^? key "paths"
- . key "/child_entities"
- . key "get"
- . key "responses"
- . key "200"
- . key "schema"
-
- liftIO $
- childGetSchema `shouldBe` Just
- [aesonQQ|
- {
- "items": {
- "$ref": "#/definitions/child_entities"
- },
- "type": "array"
- }
- |]
-
- it "includes definitions to tables" $ do
- r <- simpleBody <$> get "/"
-
- let def = r ^? key "definitions" . key "child_entities"
-
- liftIO $
-
- def `shouldBe` Just
- [aesonQQ|
- {
- "type": "object",
- "description": "child_entities comment",
- "properties": {
- "id": {
- "description": "child_entities id comment\n\nNote:\nThis is a Primary Key.",
- "format": "integer",
- "type": "integer"
- },
- "name": {
- "description": "child_entities name comment. Can be longer than sixty-three characters long",
- "format": "text",
- "type": "string"
- },
- "parent_id": {
- "description": "Note:\nThis is a Foreign Key to `entities.id`.",
- "format": "integer",
- "type": "integer"
- }
- },
- "required": [
- "id"
- ]
- }
- |]
-
- it "doesn't include privileged table for anonymous" $ do
- r <- simpleBody <$> get "/"
- let tablePath = r ^? key "paths" . key "/authors_only"
-
- liftIO $ tablePath `shouldBe` Nothing
-
- it "includes table if user has permission" $ do
- let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
- r <- simpleBody <$> request methodGet "/" [auth] ""
- let tableTag = r ^? key "paths" . key "/authors_only"
- . key "post" . key "tags"
- . nth 0
- liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|]
-
- describe "Foreign table" $
-
- it "includes foreign table properties" $ do
- r <- simpleBody <$> get "/"
-
- let method s = key "paths" . key "/projects_dump" . key s
- getSummary = r ^? method "get" . key "summary"
- getDescription = r ^? method "get" . key "description"
- getParameters = r ^? method "get" . key "parameters"
-
- liftIO $ do
-
- getSummary `shouldBe` Just "A temporary projects dump"
-
- getDescription `shouldBe` Just "Just a test for foreign tables"
-
- getParameters `shouldBe` Just
- [aesonQQ|
- [
- { "$ref": "#/parameters/rowFilter.projects_dump.id" },
- { "$ref": "#/parameters/rowFilter.projects_dump.name" },
- { "$ref": "#/parameters/rowFilter.projects_dump.client_id" },
- { "$ref": "#/parameters/select" },
- { "$ref": "#/parameters/order" },
- { "$ref": "#/parameters/range" },
- { "$ref": "#/parameters/rangeUnit" },
- { "$ref": "#/parameters/offset" },
- { "$ref": "#/parameters/limit" },
- { "$ref": "#/parameters/preferCount" }
- ]
- |]
-
- describe "Materialized view" $
-
- it "includes materialized view properties" $ do
- r <- simpleBody <$> get "/"
-
- let method s = key "paths" . key "/materialized_projects" . key s
- summary = r ^? method "get" . key "summary"
- description = r ^? method "get" . key "description"
- parameters = r ^? method "get" . key "parameters"
-
- liftIO $ do
-
- summary `shouldBe` Just "A materialized view for projects"
-
- description `shouldBe` Just "Just a test for materialized views"
-
- parameters `shouldBe` Just
- [aesonQQ|
- [
- { "$ref": "#/parameters/rowFilter.materialized_projects.id" },
- { "$ref": "#/parameters/rowFilter.materialized_projects.name" },
- { "$ref": "#/parameters/rowFilter.materialized_projects.client_id" },
- { "$ref": "#/parameters/select" },
- { "$ref": "#/parameters/order" },
- { "$ref": "#/parameters/range" },
- { "$ref": "#/parameters/rangeUnit" },
- { "$ref": "#/parameters/offset" },
- { "$ref": "#/parameters/limit" },
- { "$ref": "#/parameters/preferCount" }
- ]
- |]
-
- describe "VIEW that has a source FK based on a UNIQUE key" $
-
- it "includes fk description" $ do
- r <- simpleBody <$> get "/"
-
- let referralLink = r ^? key "definitions" . key "referrals" . key "properties" . key "link"
-
- liftIO $
- referralLink `shouldBe` Just
- [aesonQQ|
- {
- "format": "integer",
- "type": "integer",
- "description": "Note:\nThis is a Foreign Key to `pages.link`."
- }
- |]
-
- describe "PostgreSQL to Swagger Type Mapping" $ do
-
- it "character varying to string" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_character_varying"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "character varying",
- "type": "string"
- }
- |]
- it "character(1) to string" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_character"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "maxLength": 1,
- "format": "character",
- "type": "string"
- }
- |]
-
- it "text to string" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_text"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "text",
- "type": "string"
- }
- |]
-
- it "boolean to boolean" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_boolean"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "boolean",
- "type": "boolean"
- }
- |]
-
- it "smallint to integer" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_smallint"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "smallint",
- "type": "integer"
- }
- |]
-
- it "integer to integer" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_integer"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "integer",
- "type": "integer"
- }
- |]
-
- it "bigint to integer" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_bigint"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "bigint",
- "type": "integer"
- }
- |]
-
- it "numeric to number" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_numeric"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "numeric",
- "type": "number"
- }
- |]
-
- it "real to number" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_real"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "real",
- "type": "number"
- }
- |]
-
- it "double_precision to number" $ do
- r <- simpleBody <$> get "/"
-
- let types = r ^? key "definitions" . key "openapi_types" . key "properties" . key "a_double_precision"
-
- liftIO $
-
- types `shouldBe` Just
- [aesonQQ|
- {
- "format": "double precision",
- "type": "number"
- }
- |]
-
- describe "RPC" $ do
-
- it "includes function summary/description and body schema for arguments" $ do
- r <- simpleBody <$> get "/"
-
- let method s = key "paths" . key "/rpc/varied_arguments" . key s
- args = r ^? method "post" . key "parameters" . nth 0 . key "schema"
- summary = r ^? method "post" . key "summary"
- description = r ^? method "post" . key "description"
-
- liftIO $ do
-
- summary `shouldBe` Just "An RPC function"
-
- description `shouldBe` Just "Just a test for RPC function arguments"
-
- args `shouldBe` Just
- [aesonQQ|
- {
- "required": [
- "double",
- "varchar",
- "boolean",
- "date",
- "money",
- "enum",
- "arr"
- ],
- "properties": {
- "double": {
- "format": "double precision",
- "type": "number"
- },
- "varchar": {
- "format": "character varying",
- "type": "string"
- },
- "boolean": {
- "format": "boolean",
- "type": "boolean"
- },
- "date": {
- "format": "date",
- "type": "string"
- },
- "money": {
- "format": "money",
- "type": "string"
- },
- "enum": {
- "format": "enum_menagerie_type",
- "type": "string"
- },
- "arr": {
- "format": "text[]",
- "type": "string"
- },
- "integer": {
- "format": "integer",
- "type": "integer"
- },
- "json": {
- "format": "json",
- "type": "string"
- },
- "jsonb": {
- "format": "jsonb",
- "type": "string"
- }
- },
- "type": "object",
- "description": "An RPC function\n\nJust a test for RPC function arguments"
- }
- |]
-
- it "doesn't include privileged function for anonymous" $ do
- r <- simpleBody <$> get "/"
- let funcPath = r ^? key "paths" . key "/rpc/privileged_hello"
-
- liftIO $ funcPath `shouldBe` Nothing
-
- it "includes function if user has permission" $ do
- let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
- r <- simpleBody <$> request methodGet "/" [auth] ""
- let funcTag = r ^? key "paths" . key "/rpc/privileged_hello"
- . key "post" . key "tags"
- . nth 0
-
- liftIO $ funcTag `shouldBe` Just [aesonQQ|"(rpc) privileged_hello"|]
-
- it "doesn't include OUT params of function as required parameters" $ do
- r <- simpleBody <$> get "/"
- let params = r ^? key "paths" . key "/rpc/many_out_params"
- . key "post" . key "parameters" . nth 0
- . key "schema". key "required"
-
- liftIO $ params `shouldBe` Nothing
-
- it "includes INOUT params(with no DEFAULT) of function as required parameters" $ do
- r <- simpleBody <$> get "/"
- let params = r ^? key "paths" . key "/rpc/many_inout_params"
- . key "post" . key "parameters" . nth 0
- . key "schema". key "required"
-
- liftIO $ params `shouldBe` Just [aesonQQ|["num", "str"]|]
-
- describe "Allow header" $ do
-
- it "includes read/write verbs for writeable table" $ do
- r <- request methodOptions "/items" [] ""
- liftIO $
- simpleHeaders r `shouldSatisfy`
- matchHeader "Allow" "GET,POST,PATCH,DELETE"
-
- it "includes read verbs for read-only table" $ do
- r <- request methodOptions "/has_count_column" [] ""
- liftIO $
- simpleHeaders r `shouldSatisfy`
- matchHeader "Allow" "GET"
diff --git a/test/Main.hs b/test/Main.hs
index 5d5a964fa..2e561b7a7 100644
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -36,6 +36,8 @@ import qualified Feature.JsonOperatorSpec
import qualified Feature.MultipleSchemaSpec
import qualified Feature.NoJwtSpec
import qualified Feature.NonexistentSchemaSpec
+import qualified Feature.OpenApiSpec
+import qualified Feature.OptionsSpec
import qualified Feature.ProxySpec
import qualified Feature.QueryLimitedSpec
import qualified Feature.QuerySpec
@@ -45,7 +47,6 @@ import qualified Feature.RootSpec
import qualified Feature.RpcPreRequestGucsSpec
import qualified Feature.RpcSpec
import qualified Feature.SingularSpec
-import qualified Feature.StructureSpec
import qualified Feature.UnicodeSpec
import qualified Feature.UpdateSpec
import qualified Feature.UpsertSpec
@@ -104,10 +105,11 @@ main = do
, ("Feature.ConcurrentSpec" , Feature.ConcurrentSpec.spec)
, ("Feature.CorsSpec" , Feature.CorsSpec.spec)
, ("Feature.JsonOperatorSpec" , Feature.JsonOperatorSpec.spec actualPgVersion)
+ , ("Feature.OpenApiSpec" , Feature.OpenApiSpec.spec)
+ , ("Feature.OptionsSpec" , Feature.OptionsSpec.spec)
, ("Feature.QuerySpec" , Feature.QuerySpec.spec actualPgVersion)
, ("Feature.EmbedDisambiguationSpec" , Feature.EmbedDisambiguationSpec.spec)
, ("Feature.RpcSpec" , Feature.RpcSpec.spec actualPgVersion)
- , ("Feature.StructureSpec" , Feature.StructureSpec.spec)
, ("Feature.AndOrParamsSpec" , Feature.AndOrParamsSpec.spec actualPgVersion)
, ("Feature.UpsertSpec" , Feature.UpsertSpec.spec)
]