2020-04-18 23:42:17 +02:00
|
|
|
|
package integration_tests
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"database/sql"
|
|
|
|
|
"encoding/json"
|
2020-04-21 16:03:05 +02:00
|
|
|
|
"io/ioutil"
|
2020-04-18 23:42:17 +02:00
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/dosco/super-graph/core"
|
2020-04-20 05:48:49 +02:00
|
|
|
|
"github.com/stretchr/testify/assert"
|
2020-04-18 23:42:17 +02:00
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func SetupSchema(t *testing.T, db *sql.DB) {
|
|
|
|
|
|
|
|
|
|
_, err := db.Exec(`
|
|
|
|
|
CREATE TABLE users (
|
|
|
|
|
id integer PRIMARY KEY,
|
|
|
|
|
full_name text
|
|
|
|
|
)`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
_, err = db.Exec(`CREATE TABLE product (
|
|
|
|
|
id integer PRIMARY KEY,
|
|
|
|
|
name text,
|
|
|
|
|
weight float
|
|
|
|
|
)`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
_, err = db.Exec(`CREATE TABLE line_item (
|
|
|
|
|
id integer PRIMARY KEY,
|
|
|
|
|
product integer REFERENCES product(id),
|
|
|
|
|
quantity integer,
|
|
|
|
|
price float
|
|
|
|
|
)`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DropSchema(t *testing.T, db *sql.DB) {
|
|
|
|
|
|
|
|
|
|
_, err := db.Exec(`DROP TABLE IF EXISTS line_item`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
_, err = db.Exec(`DROP TABLE IF EXISTS product`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
_, err = db.Exec(`DROP TABLE IF EXISTS users`)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
|
|
|
|
|
config := core.Config{}
|
|
|
|
|
config.UseAllowList = false
|
|
|
|
|
config.AllowListFile = "./allow.list"
|
|
|
|
|
config.RolesQuery = `SELECT * FROM users WHERE id = $user_id`
|
|
|
|
|
|
|
|
|
|
config.Roles = []core.Role{
|
|
|
|
|
core.Role{
|
|
|
|
|
Name: "anon",
|
|
|
|
|
Tables: []core.RoleTable{
|
|
|
|
|
core.RoleTable{Name: "users", Query: core.Query{Limit: 100}},
|
|
|
|
|
core.RoleTable{Name: "product", Query: core.Query{Limit: 100}},
|
|
|
|
|
core.RoleTable{Name: "line_item", Query: core.Query{Limit: 100}},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sg, err := core.NewSuperGraph(&config, db)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
t.Run("seed fixtures", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`mutation { products (insert: $products) { id } }`,
|
|
|
|
|
json.RawMessage(`{"products":[
|
|
|
|
|
{"id":1, "name":"Charmin Ultra Soft", "weight": 0.5},
|
|
|
|
|
{"id":2, "name":"Hand Sanitizer", "weight": 0.2},
|
|
|
|
|
{"id":3, "name":"Case of Corona", "weight": 1.2}
|
|
|
|
|
]}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"products": [{"id": 1}, {"id": 2}, {"id": 3}]}`, string(res.Data))
|
|
|
|
|
|
|
|
|
|
res, err = sg.GraphQL(ctx,
|
|
|
|
|
`mutation { line_items (insert: $line_items) { id } }`,
|
|
|
|
|
json.RawMessage(`{"line_items":[
|
|
|
|
|
{"id":5001, "product":1, "price":6.95, "quantity":10},
|
|
|
|
|
{"id":5002, "product":2, "price":10.99, "quantity":2}
|
|
|
|
|
]}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_items": [{"id": 5001}, {"id": 5002}]}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-20 05:48:49 +02:00
|
|
|
|
t.Run("get line item", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`query { line_item(id:$id) { id, price, quantity } }`,
|
|
|
|
|
json.RawMessage(`{"id":5001}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_item": {"id": 5001, "price": 6.95, "quantity": 10}}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-18 23:42:17 +02:00
|
|
|
|
t.Run("get line items", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`query { line_items { id, price, quantity } }`,
|
|
|
|
|
json.RawMessage(`{}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_items": [{"id": 5001, "price": 6.95, "quantity": 10}, {"id": 5002, "price": 10.99, "quantity": 2}]}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("update line item", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`mutation { line_item(update:$update, id:$id) { id } }`,
|
|
|
|
|
json.RawMessage(`{"id":5001, "update":{"quantity":20}}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_item": {"id": 5001}}`, string(res.Data))
|
|
|
|
|
|
|
|
|
|
res, err = sg.GraphQL(ctx,
|
|
|
|
|
`query { line_item(id:$id) { id, price, quantity } }`,
|
|
|
|
|
json.RawMessage(`{"id":5001}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_item": {"id": 5001, "price": 6.95, "quantity": 20}}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("delete line item", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`mutation { line_item(delete:true, id:$id) { id } }`,
|
|
|
|
|
json.RawMessage(`{"id":5002}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_item": {"id": 5002}}`, string(res.Data))
|
|
|
|
|
|
|
|
|
|
res, err = sg.GraphQL(ctx,
|
|
|
|
|
`query { line_items { id, price, quantity } }`,
|
|
|
|
|
json.RawMessage(`{}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_items": [{"id": 5001, "price": 6.95, "quantity": 20}]}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("nested insert", func(t *testing.T) {
|
|
|
|
|
before(t)
|
|
|
|
|
res, err := sg.GraphQL(ctx,
|
|
|
|
|
`mutation { line_items (insert: $line_item) { id, product { name } } }`,
|
|
|
|
|
json.RawMessage(`{"line_item":
|
|
|
|
|
{"id":5003, "product": { "connect": { "id": 1} }, "price":10.95, "quantity":15}
|
|
|
|
|
}`))
|
|
|
|
|
require.NoError(t, err, res.SQL())
|
|
|
|
|
require.Equal(t, `{"line_items": [{"id": 5003, "product": {"name": "Charmin Ultra Soft"}}]}`, string(res.Data))
|
|
|
|
|
})
|
|
|
|
|
|
2020-04-20 05:48:49 +02:00
|
|
|
|
t.Run("schema introspection", func(t *testing.T) {
|
|
|
|
|
before(t)
|
2020-04-21 16:03:05 +02:00
|
|
|
|
schema, err := sg.GraphQLSchema()
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
// Uncomment the following line if you need to regenerate the expected schema.
|
|
|
|
|
// ioutil.WriteFile("../introspection.graphql", []byte(schema), 0644)
|
|
|
|
|
expected, err := ioutil.ReadFile("../introspection.graphql")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, string(expected), schema)
|
2020-04-20 05:48:49 +02:00
|
|
|
|
})
|
|
|
|
|
|
2020-04-21 16:03:05 +02:00
|
|
|
|
res, err := sg.GraphQL(ctx, introspectionQuery, json.RawMessage(``))
|
2020-04-20 05:48:49 +02:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Contains(t, string(res.Data),
|
|
|
|
|
`{"queryType":{"name":"Query"},"mutationType":{"name":"Mutation"},"subscriptionType":null,"types":`)
|
2020-04-18 23:42:17 +02:00
|
|
|
|
}
|
2020-04-20 05:48:49 +02:00
|
|
|
|
|
|
|
|
|
const introspectionQuery = `
|
2020-04-21 16:03:05 +02:00
|
|
|
|
query IntrospectionQuery {
|
2020-04-20 05:48:49 +02:00
|
|
|
|
__schema {
|
|
|
|
|
queryType { name }
|
|
|
|
|
mutationType { name }
|
|
|
|
|
subscriptionType { name }
|
|
|
|
|
types {
|
|
|
|
|
...FullType
|
|
|
|
|
}
|
|
|
|
|
directives {
|
|
|
|
|
name
|
|
|
|
|
description
|
|
|
|
|
locations
|
|
|
|
|
args {
|
|
|
|
|
...InputValue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fragment FullType on __Type {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
description
|
|
|
|
|
fields(includeDeprecated: true) {
|
|
|
|
|
name
|
|
|
|
|
description
|
|
|
|
|
args {
|
|
|
|
|
...InputValue
|
|
|
|
|
}
|
|
|
|
|
type {
|
|
|
|
|
...TypeRef
|
|
|
|
|
}
|
|
|
|
|
isDeprecated
|
|
|
|
|
deprecationReason
|
|
|
|
|
}
|
|
|
|
|
inputFields {
|
|
|
|
|
...InputValue
|
|
|
|
|
}
|
|
|
|
|
interfaces {
|
|
|
|
|
...TypeRef
|
|
|
|
|
}
|
|
|
|
|
enumValues(includeDeprecated: true) {
|
|
|
|
|
name
|
|
|
|
|
description
|
|
|
|
|
isDeprecated
|
|
|
|
|
deprecationReason
|
|
|
|
|
}
|
|
|
|
|
possibleTypes {
|
|
|
|
|
...TypeRef
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fragment InputValue on __InputValue {
|
|
|
|
|
name
|
|
|
|
|
description
|
|
|
|
|
type { ...TypeRef }
|
|
|
|
|
defaultValue
|
|
|
|
|
}
|
|
|
|
|
fragment TypeRef on __Type {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
ofType {
|
|
|
|
|
kind
|
|
|
|
|
name
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`
|