mirror of
https://github.com/Airsequel/AirGQL.git
synced 2025-07-27 02:18:43 +03:00
838 lines
25 KiB
Haskell
838 lines
25 KiB
Haskell
{-# LANGUAGE QuasiQuotes #-}
|
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
|
{-# OPTIONS_GHC -Wno-missing-local-signatures #-}
|
|
|
|
module Tests.IntrospectionSpec (main) where
|
|
|
|
import Protolude (
|
|
Either (Right),
|
|
Maybe (Nothing),
|
|
Monoid (mempty),
|
|
Text,
|
|
readFile,
|
|
void,
|
|
($),
|
|
)
|
|
|
|
import Data.Aeson qualified as Ae
|
|
import Database.SQLite.Simple qualified as SS
|
|
import Database.SQLite.Simple.QQ (sql)
|
|
import Language.GraphQL.Class (gql)
|
|
import Language.GraphQL.JSON (graphql)
|
|
import System.Directory (makeAbsolute)
|
|
import System.FilePath ((</>))
|
|
import Test.Hspec (Spec, describe, it, shouldBe)
|
|
|
|
import AirGQL.GraphQL (getDerivedSchema)
|
|
import AirGQL.Lib (getEnrichedTables, insertOnly, writeOnly)
|
|
import AirGQL.Raw (raw)
|
|
import AirGQL.Types.SchemaConf (SchemaConf (accessMode), defaultSchemaConf)
|
|
import AirGQL.Utils (withRetryConn)
|
|
import Tests.Utils (dbPath, fixtureDbId, rmSpaces, testRoot, unorderedShouldBe, withTestDbConn)
|
|
|
|
|
|
main :: Spec
|
|
main = void $ do
|
|
describe "Query" $ do
|
|
it "supports a minimal introspection query" $ do
|
|
let
|
|
introspectionQuery :: Text
|
|
introspectionQuery =
|
|
[gql|
|
|
query IntrospectionQuery {
|
|
__schema {
|
|
queryType { name }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"queryType": {
|
|
"name": "Query"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode result `shouldBe` expected
|
|
|
|
it "supports an optional filter argument" $ do
|
|
let
|
|
introspectionQuery :: Text
|
|
introspectionQuery =
|
|
[gql|
|
|
query IntrospectionQuery {
|
|
__schema {
|
|
queryType {
|
|
name
|
|
fields {
|
|
name
|
|
args {
|
|
name
|
|
type {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
expected =
|
|
rmSpaces
|
|
[raw|{
|
|
"data": {
|
|
"__schema": {
|
|
"queryType": {
|
|
"name": "Query",
|
|
"fields": [
|
|
{
|
|
"name": "users",
|
|
"args": [
|
|
{ "name": "where",
|
|
"type": { "name": "users_filter" }
|
|
},
|
|
{ "name": "order_by",
|
|
"type": { "name": null }
|
|
},
|
|
{ "name": "limit",
|
|
"type": { "name": "Int" }
|
|
},
|
|
{ "name": "offset",
|
|
"type": { "name": "Int" }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "songs",
|
|
"args": [
|
|
{ "name": "where",
|
|
"type": { "name": "songs_filter" }
|
|
},
|
|
{ "name": "order_by",
|
|
"type": { "name": null }
|
|
},
|
|
{ "name": "limit",
|
|
"type": { "name": "Int" }
|
|
},
|
|
{ "name": "offset",
|
|
"type": { "name": "Int" }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "users_by_pk",
|
|
"args": [
|
|
{ "name": "email",
|
|
"type": { "name": null }
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"name": "songs_by_pk",
|
|
"args": [
|
|
{ "name": "rowid",
|
|
"type": { "name": null }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode result `shouldBe` expected
|
|
|
|
it "doesn't allow writeonly tokens to read data" $ do
|
|
let
|
|
introspectionQuery :: Text
|
|
introspectionQuery =
|
|
[gql|
|
|
query IntrospectionQuery {
|
|
__schema {
|
|
queryType { name }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": null,
|
|
"errors": [{
|
|
"locations": [{ "column":3, "line":2 }],
|
|
"message": "user error (Cannot read field using the provided token)",
|
|
"path": ["__schema"]
|
|
}]
|
|
}
|
|
|]
|
|
|
|
schema <- withRetryConn dbPath $ \conn -> do
|
|
Right tables <- getEnrichedTables conn
|
|
getDerivedSchema
|
|
defaultSchemaConf{accessMode = writeOnly}
|
|
conn
|
|
fixtureDbId
|
|
tables
|
|
|
|
Right response <-
|
|
graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode response `shouldBe` expected
|
|
|
|
describe "Mutation" $ do
|
|
it "supports introspection queries" $ do
|
|
let
|
|
introspectionQuery :: Text
|
|
introspectionQuery =
|
|
[gql|
|
|
query IntrospectionQuery {
|
|
__schema {
|
|
mutationType {
|
|
name
|
|
fields {
|
|
name
|
|
args {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
expected =
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"mutationType": {
|
|
"name": "Mutation",
|
|
"fields": [
|
|
{
|
|
"name": "insert_users",
|
|
"args": [ { "name": "objects" }, { "name": "on_conflict" } ]
|
|
},
|
|
{
|
|
"name": "update_users",
|
|
"args": [
|
|
{ "name": "where" },
|
|
{ "name": "_set" }
|
|
]
|
|
},
|
|
{
|
|
"name": "update_users_by_pk",
|
|
"args": [
|
|
{ "name": "email" },
|
|
{ "name": "_set" }
|
|
]
|
|
},
|
|
{
|
|
"name": "delete_users",
|
|
"args": [ { "name": "where" } ]
|
|
},
|
|
{
|
|
"name": "delete_users_by_pk",
|
|
"args": [ { "name": "email" } ]
|
|
},
|
|
{
|
|
"name": "insert_songs",
|
|
"args": [ { "name": "objects" }, { "name": "on_conflict" } ]
|
|
},
|
|
{
|
|
"name": "update_songs",
|
|
"args": [
|
|
{ "name": "where" },
|
|
{ "name": "_set" }
|
|
]
|
|
},
|
|
{
|
|
"name": "update_songs_by_pk",
|
|
"args": [
|
|
{ "name": "rowid" },
|
|
{ "name": "_set" }
|
|
]
|
|
},
|
|
{
|
|
"name": "delete_songs",
|
|
"args": [ { "name": "where" } ]
|
|
},
|
|
{
|
|
"name": "delete_songs_by_pk",
|
|
"args": [ { "name": "rowid" } ]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
result `unorderedShouldBe` expected
|
|
|
|
it "supports __typename on root query" $ do
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query TypeName {
|
|
__typename
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__typename" : "Query"
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode result `shouldBe` expected
|
|
|
|
it "returns fields for {query,mutation,subscription}Type" $ do
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query {
|
|
__schema {
|
|
queryType { fields { name } }
|
|
mutationType { fields { name } }
|
|
subscriptionType { fields { name } }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"queryType": {
|
|
"fields": [
|
|
{ "name": "users" },
|
|
{ "name": "songs" },
|
|
{ "name": "users_by_pk" },
|
|
{ "name": "songs_by_pk" }
|
|
]
|
|
},
|
|
"subscriptionType": null,
|
|
"mutationType": {
|
|
"fields": [
|
|
{ "name": "insert_users" },
|
|
{ "name": "update_users" },
|
|
{ "name": "delete_users" },
|
|
{ "name": "insert_songs" },
|
|
{ "name": "update_songs" },
|
|
{ "name": "delete_songs" },
|
|
{ "name": "update_users_by_pk" },
|
|
{ "name": "delete_users_by_pk" },
|
|
{ "name": "update_songs_by_pk" },
|
|
{ "name": "delete_songs_by_pk" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
result `unorderedShouldBe` expected
|
|
|
|
it "supports __typename fields" $ do
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query UsersTypeName {
|
|
users {
|
|
__typename
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"users": [
|
|
{ "__typename" : "users_row" }
|
|
]
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
SS.execute_
|
|
conn
|
|
[sql|
|
|
insert into users (name, email, created_utc)
|
|
values ('John', 'john@example.com', '2022-01-01T00:00Z')
|
|
|]
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode result `shouldBe` expected
|
|
|
|
it "returns types" $ do
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query {
|
|
__schema {
|
|
types {
|
|
kind
|
|
name
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
expected =
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"types": [
|
|
{ "kind": "OBJECT", "name": "users_row" },
|
|
{ "kind": "OBJECT", "name": "users_mutation_response" },
|
|
{ "kind": "OBJECT", "name": "users_mutation_by_pk_response" },
|
|
{ "kind": "INPUT_OBJECT", "name": "users_insert_input" },
|
|
{ "kind": "ENUM", "name": "users_column" },
|
|
{ "kind": "INPUT_OBJECT", "name": "users_upsert_on_conflict" },
|
|
{ "kind": "INPUT_OBJECT", "name": "users_set_input" },
|
|
{ "kind": "INPUT_OBJECT", "name": "users_filter" },
|
|
{ "kind": "INPUT_OBJECT", "name": "users_order_by" },
|
|
|
|
{ "kind": "OBJECT", "name": "songs_row" },
|
|
{ "kind": "OBJECT", "name": "songs_mutation_response" },
|
|
{ "kind": "OBJECT", "name": "songs_mutation_by_pk_response" },
|
|
{ "kind": "INPUT_OBJECT", "name": "songs_insert_input" },
|
|
{ "kind": "ENUM", "name": "songs_column" },
|
|
{ "kind": "INPUT_OBJECT", "name": "songs_upsert_on_conflict" },
|
|
{ "kind": "INPUT_OBJECT", "name": "songs_set_input" },
|
|
{ "kind": "INPUT_OBJECT", "name": "songs_filter" },
|
|
{ "kind": "INPUT_OBJECT", "name": "songs_order_by" },
|
|
|
|
{ "kind": "INPUT_OBJECT", "name": "IntComparison" },
|
|
{ "kind": "INPUT_OBJECT", "name": "FloatComparison" },
|
|
{ "kind": "INPUT_OBJECT", "name": "StringComparison" },
|
|
|
|
{ "kind": "ENUM", "name": "OrderingTerm" },
|
|
|
|
{ "kind": "OBJECT", "name": "Query" },
|
|
{ "kind": "OBJECT", "name": "Mutation" },
|
|
{ "kind": "SCALAR", "name": "Boolean" },
|
|
{ "kind": "SCALAR", "name": "Int" },
|
|
{ "kind": "SCALAR", "name": "Float" },
|
|
{ "kind": "SCALAR", "name": "String" },
|
|
{ "kind": "SCALAR", "name": "ID" },
|
|
{ "kind": "OBJECT", "name": "__Schema" },
|
|
{ "kind": "OBJECT", "name": "__Type" },
|
|
{ "kind": "ENUM", "name": "__TypeKind" },
|
|
{ "kind": "OBJECT", "name": "__Field" },
|
|
{ "kind": "OBJECT", "name": "__InputValue" },
|
|
{ "kind": "OBJECT", "name": "__EnumValue" },
|
|
{ "kind": "OBJECT", "name": "__Directive" },
|
|
{ "kind": "ENUM", "name": "__DirectiveLocation" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
result `unorderedShouldBe` expected
|
|
|
|
it "returns directives on __schema" $ do
|
|
let
|
|
introspectionQuery :: Text =
|
|
[gql|
|
|
query UsersTypeName {
|
|
__schema {
|
|
directives {
|
|
name
|
|
description
|
|
locations
|
|
args {
|
|
name
|
|
description
|
|
defaultValue
|
|
type { ...TypeRef }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"directives": [
|
|
{
|
|
"name": "skip",
|
|
"args": [
|
|
{
|
|
"name": "if",
|
|
"defaultValue": null,
|
|
"type": {
|
|
"kind": "NON_NULL",
|
|
"name": null,
|
|
"ofType": {
|
|
"kind":"SCALAR",
|
|
"name":"Boolean",
|
|
"ofType":null
|
|
}
|
|
},
|
|
"description": "Skipped when true."
|
|
}
|
|
],
|
|
"locations": [
|
|
"INLINE_FRAGMENT",
|
|
"FRAGMENT_SPREAD",
|
|
"FIELD"
|
|
],
|
|
"description": "Directs the executor to skip this field or fragment when the `if` argument is true."
|
|
},
|
|
{
|
|
"name": "include",
|
|
"args": [
|
|
{
|
|
"name": "if",
|
|
"defaultValue": null,
|
|
"type": {
|
|
"kind": "NON_NULL",
|
|
"name": null,
|
|
"ofType": {
|
|
"kind":"SCALAR",
|
|
"name":"Boolean",
|
|
"ofType":null
|
|
}
|
|
},
|
|
"description": "Included when true."
|
|
}
|
|
],
|
|
"locations": [
|
|
"INLINE_FRAGMENT",
|
|
"FRAGMENT_SPREAD",
|
|
"FIELD"
|
|
],
|
|
"description":
|
|
"Directs the executor to include this field or fragment only when the `if` argument is true."
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
Ae.encode result `shouldBe` expected
|
|
|
|
it "supports a full introspection query" $ do
|
|
gqlFile <- makeAbsolute $ testRoot </> "introspection_query.gql"
|
|
introspectionQuery <- readFile gqlFile
|
|
|
|
jsonFile <- makeAbsolute $ testRoot </> "introspection_result.json"
|
|
expected <- readFile jsonFile
|
|
|
|
conn <- SS.open dbPath
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
-- Uncomment to write the new file to disk (for easier diffing)
|
|
-- P.writeFile (testRoot </> "new_introspection_result.json") $
|
|
-- P.decodeUtf8 $
|
|
-- BL.toStrict $
|
|
-- Ae.encode result
|
|
|
|
result `unorderedShouldBe` expected
|
|
|
|
it "doesn't allow writeonly tokens to return data" $ do
|
|
let dbName = "no-writeonly-return.db"
|
|
withTestDbConn dbName $ \conn -> do
|
|
SS.execute_
|
|
conn
|
|
[sql|
|
|
CREATE TABLE items (
|
|
id INTEGER PRIMARY KEY
|
|
)
|
|
|]
|
|
|
|
let
|
|
query :: Text
|
|
query =
|
|
[gql|
|
|
mutation items {
|
|
update_items(filter: { id: { eq: 0 }}, set: { id: 0 }) {
|
|
returning { id }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": null,
|
|
"errors": [{
|
|
"locations": [{ "column":5, "line":3 }],
|
|
"message": "Cannot query field \"returning\" on type \"items_mutation_response\"."
|
|
}]
|
|
}
|
|
|]
|
|
|
|
Right tables <- getEnrichedTables conn
|
|
schema <-
|
|
getDerivedSchema
|
|
defaultSchemaConf{accessMode = writeOnly}
|
|
conn
|
|
fixtureDbId
|
|
tables
|
|
|
|
Right response <-
|
|
graphql schema Nothing mempty query
|
|
|
|
Ae.encode response `shouldBe` expected
|
|
|
|
it "doesn't allow insertonly tokens to update data" $ do
|
|
let dbName = "no-insertonly-return.db"
|
|
withTestDbConn dbName $ \conn -> do
|
|
SS.execute_
|
|
conn
|
|
[sql|
|
|
CREATE TABLE items (
|
|
id INTEGER PRIMARY KEY
|
|
)
|
|
|]
|
|
|
|
let
|
|
query :: Text
|
|
query =
|
|
[gql|
|
|
mutation items {
|
|
update_items_by_pk(id: 0, set: { id: 0 }) {
|
|
affected_rows
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
rmSpaces
|
|
[raw|
|
|
{
|
|
"data": null,
|
|
"errors": [{
|
|
"locations": [{ "column":3, "line":2 }],
|
|
"message": "Cannot query field \"update_items_by_pk\" on type \"Mutation\"."
|
|
}]
|
|
}
|
|
|]
|
|
|
|
Right tables <- getEnrichedTables conn
|
|
schema <-
|
|
getDerivedSchema
|
|
defaultSchemaConf{accessMode = insertOnly}
|
|
conn
|
|
fixtureDbId
|
|
tables
|
|
|
|
Right response <-
|
|
graphql schema Nothing mempty query
|
|
|
|
Ae.encode response `shouldBe` expected
|
|
|
|
describe "Naming conflicts" $ do
|
|
it "appends _ at the end of queries to avoid conflicts with table names" $ do
|
|
let dbName = "by-pk-table-names.db"
|
|
withTestDbConn dbName $ \conn -> do
|
|
SS.execute_ conn [sql| CREATE TABLE foo (id INT) |]
|
|
SS.execute_ conn [sql| CREATE TABLE foo_by_pk (id INT) |]
|
|
SS.execute_ conn [sql| CREATE TABLE foo_by_pk_ (id INT) |]
|
|
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query {
|
|
__schema {
|
|
queryType { fields { name } }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"queryType": {
|
|
"fields": [
|
|
{ "name": "foo" },
|
|
{ "name": "foo_by_pk" },
|
|
{ "name": "foo_by_pk_" },
|
|
{ "name": "foo_by_pk__" },
|
|
{ "name": "foo_by_pk_by_pk" },
|
|
{ "name": "foo_by_pk__by_pk" }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
result `unorderedShouldBe` expected
|
|
|
|
it "appends _ at the end of argument names to avoid conflicts" $ do
|
|
let dbName = "by-pk-arg-names.db"
|
|
withTestDbConn dbName $ \conn -> do
|
|
SS.execute_ conn [sql| CREATE TABLE foo ("_set" INT PRIMARY KEY) |]
|
|
|
|
let
|
|
introspectionQuery =
|
|
[gql|
|
|
query {
|
|
__schema {
|
|
mutationType { fields { name, args { name } } }
|
|
}
|
|
}
|
|
|]
|
|
|
|
expected =
|
|
[raw|
|
|
{
|
|
"data": {
|
|
"__schema": {
|
|
"mutationType": {
|
|
"fields": [
|
|
{
|
|
"name": "insert_foo",
|
|
"args": [
|
|
{ "name": "objects" },
|
|
{ "name": "on_conflict" }
|
|
]
|
|
},
|
|
{
|
|
"name": "update_foo",
|
|
"args": [
|
|
{ "name": "_set" },
|
|
{ "name": "where" }
|
|
]
|
|
},
|
|
{
|
|
"name": "update_foo_by_pk",
|
|
"args": [
|
|
{ "name": "_set" },
|
|
{ "name": "_set_" }
|
|
]
|
|
},
|
|
{
|
|
"name": "delete_foo",
|
|
"args": [{ "name": "where" }]
|
|
},
|
|
{
|
|
"name": "delete_foo_by_pk",
|
|
"args": [{ "name": "_set" }]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|]
|
|
|
|
Right tables <- getEnrichedTables conn
|
|
schema <- getDerivedSchema defaultSchemaConf conn fixtureDbId tables
|
|
|
|
Right result <- graphql schema Nothing mempty introspectionQuery
|
|
|
|
result `unorderedShouldBe` expected
|