Files
postgrest/test/Feature/StructureSpec.hs
T
Joe NelsonandGitHub acd75a3997 Merge pull request #639 from hudayou/swagger2
Provide a swagger2 spec for the dynamic API
2016-06-20 15:12:58 -07:00

431 lines
13 KiB
Haskell

module Feature.StructureSpec where
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import SpecHelper
import Network.HTTP.Types
import Network.Wai (Application)
import Network.Wai.Test (SResponse(simpleStatus, simpleHeaders, simpleBody))
import Data.Maybe (fromJust)
import Data.Aeson (decode)
import qualified Data.JsonSchema.Draft4 as D4
spec :: SpecWith Application
spec = do
describe "GET /" $ do
it "lists views in schema" $
request methodGet "/" [] ""
`shouldRespondWith` [json| [
{"schema":"test","name":"Escap3e;","insertable":true}
, {"schema":"test","name":"addresses","insertable":true}
, {"schema":"test","name":"articleStars","insertable":true}
, {"schema":"test","name":"articles","insertable":true}
, {"schema":"test","name":"auto_incrementing_pk","insertable":true}
, {"schema":"test","name":"clients","insertable":true}
, {"schema":"test","name":"comments","insertable":true}
, {"schema":"test","name":"complex_items","insertable":true}
, {"schema":"test","name":"compound_pk","insertable":true}
, {"schema":"test","name":"empty_table","insertable":true}
, {"schema":"test","name":"filtered_tasks","insertable":true}
, {"schema":"test","name":"ghostBusters","insertable":true}
, {"schema":"test","name":"has_count_column","insertable":false}
, {"schema":"test","name":"has_fk","insertable":true}
, {"schema":"test","name":"insertable_view_with_join","insertable":true}
, {"schema":"test","name":"insertonly","insertable":true}
, {"schema":"test","name":"items","insertable":true}
, {"schema":"test","name":"json","insertable":true}
, {"schema":"test","name":"materialized_view","insertable":false}
, {"schema":"test","name":"menagerie","insertable":true}
, {"schema":"test","name":"no_pk","insertable":true}
, {"schema":"test","name":"nullable_integer","insertable":true}
, {"schema":"test","name":"orders","insertable":true}
, {"schema":"test","name":"projects","insertable":true}
, {"schema":"test","name":"projects_view","insertable":true}
, {"schema":"test","name":"simple_pk","insertable":true}
, {"schema":"test","name":"tasks","insertable":true}
, {"schema":"test","name":"tsearch","insertable":true}
, {"schema":"test","name":"users","insertable":true}
, {"schema":"test","name":"users_projects","insertable":true}
, {"schema":"test","name":"users_tasks","insertable":true}
, {"schema":"test","name":"withUnique","insertable":true}
] |]
{matchStatus = 200}
it "lists only views user has permission to see" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
request methodGet "/" [auth] ""
`shouldRespondWith` [json| [
{"schema":"test","name":"authors_only","insertable":true}
] |]
{matchStatus = 200}
it "returns a valid openapi spec" $ do
r <- request methodGet "/" [("Accept", "application/openapi+json")] ""
liftIO $
let respStatus = simpleStatus r in
respStatus `shouldSatisfy`
\s -> s == Status { statusCode = 200, statusMessage="OK" }
liftIO $
let respHeaders = simpleHeaders r in
respHeaders `shouldSatisfy`
\hs -> ("Content-Type", "application/openapi+json; charset=utf-8") `elem` hs
liftIO $
let respBody = simpleBody r
schema :: D4.Schema
schema = D4.emptySchema { D4._schemaRef = Just "openapi.json" }
schemaContext :: D4.SchemaWithURI D4.Schema
schemaContext = D4.SchemaWithURI
{ D4._swSchema = schema
, D4._swURI = Just "test/fixtures/openapi.json"
}
in
D4.fetchFilesystemAndValidate schemaContext ((fromJust . decode) respBody) `shouldReturn` Right ()
it "should respond to openapi request on none root path with 415" $
request methodGet "/none_root_path"
(acceptHdrs "application/openapi+json") ""
`shouldRespondWith` 415
describe "Table info" $ do
it "The structure of complex views is correctly detected" $
request methodOptions "/filtered_tasks" [] "" `shouldRespondWith`
[json|
{
"pkey": [
"myId"
],
"columns": [
{
"references": null,
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "myId",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 1
},
{
"references": null,
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "name",
"type": "text",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 2
},
{
"references": {
"schema": "test",
"column": "id",
"table": "projects"
},
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "projectID",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 3
}
]
}
|]
it "is available with OPTIONS verb" $
request methodOptions "/menagerie" [] "" `shouldRespondWith`
[json|
{
"pkey":["integer"],
"columns":[
{
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "integer",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": false,
"position": 1,
"references": null,
"default": null
}, {
"default": null,
"precision": 53,
"updatable": true,
"schema": "test",
"name": "double",
"type": "double precision",
"maxLen": null,
"enum": [],
"nullable": false,
"references": null,
"position": 2
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "varchar",
"type": "character varying",
"maxLen": null,
"enum": [],
"nullable": false,
"position": 3,
"references": null,
"default": null
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "boolean",
"type": "boolean",
"maxLen": null,
"enum": [],
"nullable": false,
"references": null,
"position": 4
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "date",
"type": "date",
"maxLen": null,
"enum": [],
"nullable": false,
"references": null,
"position": 5
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "money",
"type": "money",
"maxLen": null,
"enum": [],
"nullable": false,
"position": 6,
"references": null,
"default": null
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "enum",
"type": "test.enum_menagerie_type",
"maxLen": null,
"enum": [
"foo",
"bar"
],
"nullable": false,
"position": 7,
"references": null,
"default": null
}
]
}
|]
it "it includes primary and foreign keys for views" $
request methodOptions "/projects_view" [] "" `shouldRespondWith`
[json|
{
"pkey":[
"id"
],
"columns":[
{
"references":null,
"default":null,
"precision":32,
"updatable":true,
"schema":"test",
"name":"id",
"type":"integer",
"maxLen":null,
"enum":[],
"nullable":true,
"position":1
},
{
"references":null,
"default":null,
"precision":null,
"updatable":true,
"schema":"test",
"name":"name",
"type":"text",
"maxLen":null,
"enum":[],
"nullable":true,
"position":2
},
{
"references": {
"schema":"test",
"column":"id",
"table":"clients"
},
"default":null,
"precision":32,
"updatable":true,
"schema":"test",
"name":"client_id",
"type":"integer",
"maxLen":null,
"enum":[],
"nullable":true,
"position":3
}
]
}
|]
it "includes foreign key data" $
request methodOptions "/has_fk" [] ""
`shouldRespondWith` [json|
{
"pkey": ["id"],
"columns":[
{
"default": "nextval('test.has_fk_id_seq'::regclass)",
"precision": 64,
"updatable": true,
"schema": "test",
"name": "id",
"type": "bigint",
"maxLen": null,
"nullable": false,
"position": 1,
"enum": [],
"references": null
}, {
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "auto_inc_fk",
"type": "integer",
"maxLen": null,
"nullable": true,
"position": 2,
"enum": [],
"references": {"schema":"test", "table": "auto_incrementing_pk", "column": "id"}
}, {
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "simple_fk",
"type": "character varying",
"maxLen": 255,
"nullable": true,
"position": 3,
"enum": [],
"references": {"schema":"test", "table": "simple_pk", "column": "k"}
}
]
}
|]
it "includes all information on views for renamed columns, and raises relations to correct schema" $
request methodOptions "/articleStars" [] ""
`shouldRespondWith` [json|
{
"pkey": [
"articleId",
"userId"
],
"columns": [
{
"references": {
"schema": "test",
"column": "id",
"table": "articles"
},
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "articleId",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 1
},
{
"references": {
"schema": "test",
"column": "id",
"table": "users"
},
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "userId",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 2
},
{
"references": null,
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "createdAt",
"type": "timestamp without time zone",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 3
}
]
}
|]
it "errors for non existant tables" $
request methodOptions "/dne" [] "" `shouldRespondWith` 404
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"