graphql_devise 0.11.3 → 0.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.travis.yml +7 -0
- data/Appraisals +14 -0
- data/CHANGELOG.md +48 -1
- data/README.md +181 -20
- data/app/controllers/graphql_devise/application_controller.rb +4 -1
- data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +25 -0
- data/app/controllers/graphql_devise/graphql_controller.rb +2 -0
- data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
- data/app/views/graphql_devise/mailer/confirmation_instructions.html.erb +1 -1
- data/app/views/graphql_devise/mailer/reset_password_instructions.html.erb +1 -1
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +2 -0
- data/graphql_devise.gemspec +6 -4
- data/lib/generators/graphql_devise/install_generator.rb +28 -5
- data/lib/graphql_devise.rb +24 -10
- data/lib/graphql_devise/default_operations/mutations.rb +6 -6
- data/lib/graphql_devise/default_operations/resolvers.rb +2 -2
- data/lib/graphql_devise/errors/authentication_error.rb +7 -0
- data/lib/graphql_devise/{detailed_user_error.rb → errors/detailed_user_error.rb} +1 -1
- data/lib/graphql_devise/errors/error_codes.rb +6 -0
- data/lib/graphql_devise/errors/execution_error.rb +4 -0
- data/lib/graphql_devise/{user_error.rb → errors/user_error.rb} +1 -1
- data/lib/graphql_devise/mount_method/operation_preparer.rb +2 -2
- data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +6 -2
- data/lib/graphql_devise/mount_method/operation_preparers/gql_name_setter.rb +1 -1
- data/lib/graphql_devise/mount_method/operation_preparers/mutation_field_setter.rb +3 -2
- data/lib/graphql_devise/mount_method/operation_preparers/resolver_type_setter.rb +1 -1
- data/lib/graphql_devise/mount_method/operation_preparers/resource_name_setter.rb +2 -2
- data/lib/graphql_devise/mutations/resend_confirmation.rb +3 -5
- data/lib/graphql_devise/mutations/send_password_reset.rb +5 -2
- data/lib/graphql_devise/mutations/sign_up.rb +3 -6
- data/lib/graphql_devise/rails/routes.rb +5 -72
- data/lib/graphql_devise/resource_loader.rb +87 -0
- data/lib/graphql_devise/schema_plugin.rb +106 -0
- data/lib/graphql_devise/version.rb +1 -1
- data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +41 -3
- data/spec/dummy/app/controllers/application_controller.rb +1 -0
- data/spec/dummy/app/graphql/dummy_schema.rb +18 -0
- data/spec/dummy/app/graphql/interpreter_schema.rb +9 -0
- data/spec/dummy/app/graphql/types/mutation_type.rb +1 -1
- data/spec/dummy/app/graphql/types/query_type.rb +10 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/generators/graphql_devise/install_generator_spec.rb +21 -0
- data/spec/rails_helper.rb +4 -1
- data/spec/requests/graphql_controller_spec.rb +80 -0
- data/spec/requests/mutations/resend_confirmation_spec.rb +44 -29
- data/spec/requests/mutations/send_password_reset_spec.rb +40 -12
- data/spec/requests/queries/confirm_account_spec.rb +7 -1
- data/spec/requests/user_controller_spec.rb +189 -24
- data/spec/services/mount_method/operation_preparer_spec.rb +8 -3
- data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +1 -1
- data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +15 -8
- data/spec/services/mount_method/operation_preparers/mutation_field_setter_spec.rb +18 -4
- data/spec/services/mount_method/operation_preparers/resource_name_setter_spec.rb +1 -1
- data/spec/services/resource_loader_spec.rb +82 -0
- data/spec/services/schema_plugin_spec.rb +26 -0
- metadata +107 -87
- data/lib/graphql_devise/error_codes.rb +0 -5
- data/spec/support/generators/file_helpers.rb +0 -12
@@ -0,0 +1,87 @@
|
|
1
|
+
module GraphqlDevise
|
2
|
+
class ResourceLoader
|
3
|
+
def initialize(resource, options = {}, routing = false)
|
4
|
+
@resource = resource
|
5
|
+
@options = options
|
6
|
+
@routing = routing
|
7
|
+
@default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(query, mutation)
|
11
|
+
mapping_name = @resource.to_s.underscore.tr('/', '_').to_sym
|
12
|
+
|
13
|
+
# clean_options responds to all keys defined in GraphqlDevise::MountMethod::SUPPORTED_OPTIONS
|
14
|
+
clean_options = GraphqlDevise::MountMethod::OptionSanitizer.new(@options).call!
|
15
|
+
|
16
|
+
return clean_options if GraphqlDevise.resource_mounted?(mapping_name) && @routing
|
17
|
+
|
18
|
+
validate_options!(clean_options)
|
19
|
+
|
20
|
+
authenticatable_type = clean_options.authenticatable_type.presence ||
|
21
|
+
"Types::#{@resource}Type".safe_constantize ||
|
22
|
+
GraphqlDevise::Types::AuthenticatableType
|
23
|
+
|
24
|
+
prepared_mutations = prepare_mutations(mapping_name, clean_options, authenticatable_type)
|
25
|
+
|
26
|
+
if prepared_mutations.any? && mutation.blank?
|
27
|
+
raise GraphqlDevise::Error, 'You need to provide a mutation type unless all mutations are skipped'
|
28
|
+
end
|
29
|
+
|
30
|
+
prepared_mutations.each do |action, prepared_mutation|
|
31
|
+
mutation.field(action, mutation: prepared_mutation, authenticate: false)
|
32
|
+
end
|
33
|
+
|
34
|
+
prepared_resolvers = prepare_resolvers(mapping_name, clean_options, authenticatable_type)
|
35
|
+
|
36
|
+
if prepared_resolvers.any? && query.blank?
|
37
|
+
raise GraphqlDevise::Error, 'You need to provide a query type unless all queries are skipped'
|
38
|
+
end
|
39
|
+
|
40
|
+
prepared_resolvers.each do |action, resolver|
|
41
|
+
query.field(action, resolver: resolver, authenticate: false)
|
42
|
+
end
|
43
|
+
|
44
|
+
GraphqlDevise.add_mapping(mapping_name, @resource)
|
45
|
+
GraphqlDevise.mount_resource(mapping_name) if @routing
|
46
|
+
|
47
|
+
clean_options
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def prepare_resolvers(mapping_name, clean_options, authenticatable_type)
|
53
|
+
GraphqlDevise::MountMethod::OperationPreparer.new(
|
54
|
+
mapping_name: mapping_name,
|
55
|
+
custom: clean_options.operations,
|
56
|
+
additional_operations: clean_options.additional_queries,
|
57
|
+
preparer: GraphqlDevise::MountMethod::OperationPreparers::ResolverTypeSetter.new(authenticatable_type),
|
58
|
+
selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
|
59
|
+
default: GraphqlDevise::DefaultOperations::QUERIES, only: clean_options.only, skipped: clean_options.skip
|
60
|
+
)
|
61
|
+
).call
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare_mutations(mapping_name, clean_options, authenticatable_type)
|
65
|
+
GraphqlDevise::MountMethod::OperationPreparer.new(
|
66
|
+
mapping_name: mapping_name,
|
67
|
+
custom: clean_options.operations,
|
68
|
+
additional_operations: clean_options.additional_mutations,
|
69
|
+
preparer: GraphqlDevise::MountMethod::OperationPreparers::MutationFieldSetter.new(authenticatable_type),
|
70
|
+
selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
|
71
|
+
default: GraphqlDevise::DefaultOperations::MUTATIONS, only: clean_options.only, skipped: clean_options.skip
|
72
|
+
)
|
73
|
+
).call
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_options!(clean_options)
|
77
|
+
GraphqlDevise::MountMethod::OptionsValidator.new(
|
78
|
+
[
|
79
|
+
GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: clean_options),
|
80
|
+
GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new(
|
81
|
+
options: clean_options, supported_operations: @default_operations
|
82
|
+
)
|
83
|
+
]
|
84
|
+
).validate!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module GraphqlDevise
|
2
|
+
class SchemaPlugin
|
3
|
+
DEFAULT_NOT_AUTHENTICATED = ->(field) { raise GraphqlDevise::AuthenticationError, "#{field} field requires authentication" }
|
4
|
+
|
5
|
+
def initialize(query: nil, mutation: nil, authenticate_default: true, resource_loaders: [], unauthenticated_proc: DEFAULT_NOT_AUTHENTICATED)
|
6
|
+
@query = query
|
7
|
+
@mutation = mutation
|
8
|
+
@resource_loaders = resource_loaders
|
9
|
+
@authenticate_default = authenticate_default
|
10
|
+
@unauthenticated_proc = unauthenticated_proc
|
11
|
+
|
12
|
+
# Must happen on initialize so operations are loaded before the types are added to the schema on GQL < 1.10
|
13
|
+
load_fields
|
14
|
+
end
|
15
|
+
|
16
|
+
def use(schema_definition)
|
17
|
+
schema_definition.tracer(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def trace(event, trace_data)
|
21
|
+
# Authenticate only root level queries
|
22
|
+
return yield unless event == 'execute_field' && path(trace_data).count == 1
|
23
|
+
|
24
|
+
field = traced_field(trace_data)
|
25
|
+
provided_value = authenticate_option(field, trace_data)
|
26
|
+
context = set_current_resource(context_from_data(trace_data))
|
27
|
+
|
28
|
+
if !provided_value.nil?
|
29
|
+
raise_on_missing_resource(context, field) if provided_value
|
30
|
+
elsif @authenticate_default
|
31
|
+
raise_on_missing_resource(context, field)
|
32
|
+
end
|
33
|
+
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_current_resource(context)
|
40
|
+
controller = context[:controller]
|
41
|
+
resource_names = Array(context[:resource_name])
|
42
|
+
context[:current_resource] = resource_names.find do |resource_name|
|
43
|
+
unless Devise.mappings.key?(resource_name)
|
44
|
+
raise(
|
45
|
+
GraphqlDevise::Error,
|
46
|
+
"Invalid resource_name `#{resource_name}` provided to `graphql_context`. Possible values are: #{Devise.mappings.keys}."
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
found = controller.set_resource_by_token(resource_name)
|
51
|
+
break found if found
|
52
|
+
end
|
53
|
+
|
54
|
+
context
|
55
|
+
end
|
56
|
+
|
57
|
+
def raise_on_missing_resource(context, field)
|
58
|
+
@unauthenticated_proc.call(field.name) if context[:current_resource].blank?
|
59
|
+
end
|
60
|
+
|
61
|
+
def context_from_data(trace_data)
|
62
|
+
query = if trace_data[:context]
|
63
|
+
trace_data[:context].query
|
64
|
+
else
|
65
|
+
trace_data[:query]
|
66
|
+
end
|
67
|
+
|
68
|
+
query.context
|
69
|
+
end
|
70
|
+
|
71
|
+
def path(trace_data)
|
72
|
+
if trace_data[:context]
|
73
|
+
trace_data[:context].path
|
74
|
+
else
|
75
|
+
trace_data[:path]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def traced_field(trace_data)
|
80
|
+
if trace_data[:context]
|
81
|
+
trace_data[:context].field
|
82
|
+
else
|
83
|
+
trace_data[:field]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def authenticate_option(field, trace_data)
|
88
|
+
if trace_data[:context]
|
89
|
+
field.metadata[:authenticate]
|
90
|
+
else
|
91
|
+
field.graphql_definition.metadata[:authenticate]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def load_fields
|
96
|
+
@resource_loaders.each do |resource_loader|
|
97
|
+
raise Error, 'Invalid resource loader instance' unless resource_loader.instance_of?(GraphqlDevise::ResourceLoader)
|
98
|
+
|
99
|
+
resource_loader.call(@query, @mutation)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
GraphQL::Field.accepts_definitions(authenticate: GraphQL::Define.assign_metadata_key(:authenticate))
|
106
|
+
GraphQL::Schema::Field.accepts_definition(:authenticate)
|
@@ -3,10 +3,48 @@ module Api
|
|
3
3
|
class GraphqlController < ApplicationController
|
4
4
|
include GraphqlDevise::Concerns::SetUserByToken
|
5
5
|
|
6
|
-
before_action :authenticate_user!
|
7
|
-
|
8
6
|
def graphql
|
9
|
-
|
7
|
+
result = DummySchema.execute(params[:query], execute_params(params))
|
8
|
+
|
9
|
+
render json: result unless performed?
|
10
|
+
end
|
11
|
+
|
12
|
+
def interpreter
|
13
|
+
render json: InterpreterSchema.execute(params[:query], execute_params(params))
|
14
|
+
end
|
15
|
+
|
16
|
+
def failing_resource_name
|
17
|
+
render json: DummySchema.execute(params[:query], context: graphql_context([:user, :fail]))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def execute_params(item)
|
23
|
+
{
|
24
|
+
operation_name: item[:operationName],
|
25
|
+
variables: ensure_hash(item[:variables]),
|
26
|
+
context: graphql_context(:user)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def ensure_hash(ambiguous_param)
|
31
|
+
case ambiguous_param
|
32
|
+
when String
|
33
|
+
if ambiguous_param.present?
|
34
|
+
ensure_hash(JSON.parse(ambiguous_param))
|
35
|
+
else
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
when Hash, ActionController::Parameters
|
39
|
+
ambiguous_param
|
40
|
+
when nil
|
41
|
+
{}
|
42
|
+
else
|
43
|
+
raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def verify_authenticity_token
|
10
48
|
end
|
11
49
|
end
|
12
50
|
end
|
@@ -1,4 +1,22 @@
|
|
1
1
|
class DummySchema < GraphQL::Schema
|
2
|
+
use GraphqlDevise::SchemaPlugin.new(
|
3
|
+
query: Types::QueryType,
|
4
|
+
mutation: Types::MutationType,
|
5
|
+
resource_loaders: [
|
6
|
+
GraphqlDevise::ResourceLoader.new(
|
7
|
+
'User',
|
8
|
+
only: [
|
9
|
+
:login,
|
10
|
+
:confirm_account,
|
11
|
+
:send_password_reset,
|
12
|
+
:resend_confirmation,
|
13
|
+
:check_password_token
|
14
|
+
]
|
15
|
+
),
|
16
|
+
GraphqlDevise::ResourceLoader.new('Guest', only: [:logout])
|
17
|
+
]
|
18
|
+
)
|
19
|
+
|
2
20
|
mutation(Types::MutationType)
|
3
21
|
query(Types::QueryType)
|
4
22
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class InterpreterSchema < GraphQL::Schema
|
2
|
+
use GraphQL::Execution::Interpreter if Gem::Version.new(GraphQL::VERSION) >= Gem::Version.new('1.9.0')
|
3
|
+
use GraphQL::Analysis::AST if Gem::Version.new(GraphQL::VERSION) >= Gem::Version.new('1.10.0')
|
4
|
+
|
5
|
+
use GraphqlDevise::SchemaPlugin.new(query: Types::QueryType, authenticate_default: false)
|
6
|
+
|
7
|
+
mutation(Types::MutationType)
|
8
|
+
query(Types::QueryType)
|
9
|
+
end
|
@@ -1,5 +1,15 @@
|
|
1
1
|
module Types
|
2
2
|
class QueryType < Types::BaseObject
|
3
3
|
field :user, resolver: Resolvers::UserShow
|
4
|
+
field :public_field, String, null: false, authenticate: false
|
5
|
+
field :private_field, String, null: false, authenticate: true
|
6
|
+
|
7
|
+
def public_field
|
8
|
+
'Field does not require authentication'
|
9
|
+
end
|
10
|
+
|
11
|
+
def private_field
|
12
|
+
'Field will always require authentication'
|
13
|
+
end
|
4
14
|
end
|
5
15
|
end
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -27,5 +27,8 @@ Rails.application.routes.draw do
|
|
27
27
|
at: '/api/v1/user_customer/graphql_auth'
|
28
28
|
)
|
29
29
|
|
30
|
+
get '/api/v1/graphql', to: 'api/v1/graphql#graphql'
|
30
31
|
post '/api/v1/graphql', to: 'api/v1/graphql#graphql'
|
32
|
+
post '/api/v1/interpreter', to: 'api/v1/graphql#interpreter'
|
33
|
+
post '/api/v1/failing', to: 'api/v1/graphql#failing_resource_name'
|
31
34
|
end
|
@@ -17,6 +17,24 @@ RSpec.describe GraphqlDevise::InstallGenerator, type: :generator do
|
|
17
17
|
run_generator(args)
|
18
18
|
end
|
19
19
|
|
20
|
+
context 'when mount option is schema' do
|
21
|
+
let(:args) { ['Admin', '--mount', 'GqldDummySchema'] }
|
22
|
+
|
23
|
+
it 'mounts the SchemaPlugin' do
|
24
|
+
assert_file 'config/initializers/devise.rb'
|
25
|
+
assert_file 'config/initializers/devise_token_auth.rb', /^\s{2}#{Regexp.escape('config.change_headers_on_each_request = false')}/
|
26
|
+
assert_file 'config/locales/devise.en.yml'
|
27
|
+
|
28
|
+
assert_migration 'db/migrate/devise_token_auth_create_admins.rb'
|
29
|
+
|
30
|
+
assert_file 'app/models/admin.rb', /^\s{2}devise :.+include GraphqlDevise::Concerns::Model/m
|
31
|
+
|
32
|
+
assert_file 'app/controllers/application_controller.rb', /^\s{2}include GraphqlDevise::Concerns::SetUserByToken/
|
33
|
+
|
34
|
+
assert_file 'app/graphql/gqld_dummy_schema.rb', /\s+#{Regexp.escape("GraphqlDevise::ResourceLoader.new('Admin')")}/
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
20
38
|
context 'when passing no params to the generator' do
|
21
39
|
let(:args) { [] }
|
22
40
|
|
@@ -59,5 +77,8 @@ RSpec.describe GraphqlDevise::InstallGenerator, type: :generator do
|
|
59
77
|
FileUtils.cd(File.join(destination_root, '..')) do
|
60
78
|
`rails new gqld_dummy -S -C --skip-action-mailbox --skip-action-text -T --skip-spring --skip-bundle --skip-keeps -G --skip-active-storage -J --skip-listen --skip-bootsnap`
|
61
79
|
end
|
80
|
+
FileUtils.cd(File.join(destination_root, '../gqld_dummy')) do
|
81
|
+
`rails generate graphql:install`
|
82
|
+
end
|
62
83
|
end
|
63
84
|
end
|
data/spec/rails_helper.rb
CHANGED
@@ -38,5 +38,8 @@ RSpec.configure do |config|
|
|
38
38
|
config.include(Requests::JsonHelpers, type: :request)
|
39
39
|
config.include(Requests::AuthHelpers, type: :request)
|
40
40
|
config.include(ActiveSupport::Testing::TimeHelpers)
|
41
|
-
|
41
|
+
|
42
|
+
config.before(:suite) do
|
43
|
+
ActionController::Base.allow_forgery_protection = true
|
44
|
+
end
|
42
45
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
RSpec.describe GraphqlDevise::GraphqlController do
|
4
|
+
let(:password) { 'password123' }
|
5
|
+
let(:user) { create(:user, :confirmed, password: password) }
|
6
|
+
let(:params) { { query: query, variables: variables } }
|
7
|
+
let(:request_params) do
|
8
|
+
if Rails::VERSION::MAJOR >= 5
|
9
|
+
{ params: params }
|
10
|
+
else
|
11
|
+
params
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'when variables are a string' do
|
16
|
+
let(:variables) { "{\"email\": \"#{user.email}\"}" }
|
17
|
+
let(:query) { "mutation($email: String!) { userLogin(email: $email, password: \"#{password}\") { user { email name signInCount } } }" }
|
18
|
+
|
19
|
+
it 'parses the string variables' do
|
20
|
+
post '/api/v1/graphql_auth', request_params
|
21
|
+
|
22
|
+
expect(json_response).to match(
|
23
|
+
data: { userLogin: { user: { email: user.email, name: user.name, signInCount: 1 } } }
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when variables is an empty string' do
|
28
|
+
let(:variables) { '' }
|
29
|
+
let(:query) { "mutation { userLogin(email: \"#{user.email}\", password: \"#{password}\") { user { email name signInCount } } }" }
|
30
|
+
|
31
|
+
it 'returns an empty hash as variables' do
|
32
|
+
post '/api/v1/graphql_auth', request_params
|
33
|
+
|
34
|
+
expect(json_response).to match(
|
35
|
+
data: { userLogin: { user: { email: user.email, name: user.name, signInCount: 1 } } }
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when variables are not a string or hash' do
|
42
|
+
let(:variables) { 1 }
|
43
|
+
let(:query) { "mutation($email: String!) { userLogin(email: $email, password: \"#{password}\") { user { email name signInCount } } }" }
|
44
|
+
|
45
|
+
it 'raises an error' do
|
46
|
+
expect do
|
47
|
+
post '/api/v1/graphql_auth', request_params
|
48
|
+
end.to raise_error(ArgumentError)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when multiplexing queries' do
|
53
|
+
let(:params) do
|
54
|
+
{
|
55
|
+
_json: [
|
56
|
+
{ query: "mutation { userLogin(email: \"#{user.email}\", password: \"#{password}\") { user { email name signInCount } } }" },
|
57
|
+
{ query: "mutation { userLogin(email: \"#{user.email}\", password: \"wrong password\") { user { email name signInCount } } }" }
|
58
|
+
]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'executes multiple queries in the same request' do
|
63
|
+
post '/api/v1/graphql_auth', request_params
|
64
|
+
|
65
|
+
expect(json_response).to match(
|
66
|
+
[
|
67
|
+
{ data: { userLogin: { user: { email: user.email, name: user.name, signInCount: 1 } } } },
|
68
|
+
{
|
69
|
+
data: { userLogin: nil },
|
70
|
+
errors: [
|
71
|
+
hash_including(
|
72
|
+
message: 'Invalid login credentials. Please try again.', extensions: { code: 'USER_ERROR' }
|
73
|
+
)
|
74
|
+
]
|
75
|
+
}
|
76
|
+
]
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|