Split StructureSpec into OpenApiSpec and OptionsSpec
This commit is contained in:
committed by
Steve Chavez
parent
d91f47e582
commit
944efb3a6a
+2
-1
@@ -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
|
||||
|
||||
@@ -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.<pk/>",
|
||||
"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`.<fk table='entities' column='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`.<fk table='pages' column='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"]|]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.<pk/>",
|
||||
"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`.<fk table='entities' column='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`.<fk table='pages' column='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"
|
||||
+4
-2
@@ -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)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user