graphql_devise 0.11.0 → 0.12.0
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 +4 -1
- data/.rspec +1 -0
- data/.travis.yml +36 -18
- data/CHANGELOG.md +56 -0
- data/README.md +274 -35
- data/app/controllers/graphql_devise/application_controller.rb +4 -1
- data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +23 -0
- data/app/controllers/graphql_devise/graphql_controller.rb +2 -0
- data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
- data/config/routes.rb +18 -0
- data/graphql_devise.gemspec +3 -3
- data/lib/generators/graphql_devise/install_generator.rb +63 -30
- data/lib/graphql_devise.rb +32 -0
- data/lib/graphql_devise/concerns/controller_methods.rb +23 -0
- data/lib/graphql_devise/mount_method/operation_preparer.rb +2 -2
- data/lib/graphql_devise/mount_method/operation_preparers/resource_name_setter.rb +1 -1
- data/lib/graphql_devise/mutations/login.rb +4 -1
- data/lib/graphql_devise/mutations/resend_confirmation.rb +4 -1
- data/lib/graphql_devise/mutations/send_password_reset.rb +3 -2
- data/lib/graphql_devise/mutations/sign_up.rb +1 -9
- data/lib/graphql_devise/rails/routes.rb +5 -76
- data/lib/graphql_devise/resource_loader.rb +87 -0
- data/{app/graphql → lib}/graphql_devise/schema.rb +0 -1
- data/lib/graphql_devise/schema_plugin.rb +87 -0
- data/lib/graphql_devise/version.rb +1 -1
- data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +11 -2
- data/spec/dummy/app/controllers/application_controller.rb +1 -0
- data/spec/dummy/app/graphql/dummy_schema.rb +9 -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/environments/test.rb +1 -1
- data/spec/dummy/config/routes.rb +1 -0
- data/spec/generators/graphql_devise/install_generator_spec.rb +62 -30
- data/spec/rails_helper.rb +4 -1
- data/spec/requests/graphql_controller_spec.rb +80 -0
- data/spec/requests/mutations/login_spec.rb +14 -2
- data/spec/requests/mutations/resend_confirmation_spec.rb +24 -9
- data/spec/requests/mutations/send_password_reset_spec.rb +9 -1
- data/spec/requests/mutations/sign_up_spec.rb +10 -0
- data/spec/requests/user_controller_spec.rb +180 -24
- data/spec/services/mount_method/operation_preparer_spec.rb +2 -2
- 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 +1 -1
- 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
- data/spec/spec_helper.rb +1 -1
- metadata +33 -8
- 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,87 @@
|
|
1
|
+
module GraphqlDevise
|
2
|
+
class SchemaPlugin
|
3
|
+
DEFAULT_NOT_AUTHENTICATED = ->(field) { raise GraphqlDevise::UserError, "#{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
|
+
|
27
|
+
if !provided_value.nil?
|
28
|
+
raise_on_missing_resource(context(trace_data), field) if provided_value
|
29
|
+
elsif @authenticate_default
|
30
|
+
raise_on_missing_resource(context(trace_data), field)
|
31
|
+
end
|
32
|
+
|
33
|
+
yield
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def raise_on_missing_resource(context, field)
|
39
|
+
@unauthenticated_proc.call(field.name) if context[:current_resource].blank?
|
40
|
+
end
|
41
|
+
|
42
|
+
def context(trace_data)
|
43
|
+
query = if trace_data[:context]
|
44
|
+
trace_data[:context].query
|
45
|
+
else
|
46
|
+
trace_data[:query]
|
47
|
+
end
|
48
|
+
|
49
|
+
query.context
|
50
|
+
end
|
51
|
+
|
52
|
+
def path(trace_data)
|
53
|
+
if trace_data[:context]
|
54
|
+
trace_data[:context].path
|
55
|
+
else
|
56
|
+
trace_data[:path]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def traced_field(trace_data)
|
61
|
+
if trace_data[:context]
|
62
|
+
trace_data[:context].field
|
63
|
+
else
|
64
|
+
trace_data[:field]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def authenticate_option(field, trace_data)
|
69
|
+
if trace_data[:context]
|
70
|
+
field.metadata[:authenticate]
|
71
|
+
else
|
72
|
+
field.graphql_definition.metadata[:authenticate]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_fields
|
77
|
+
@resource_loaders.each do |resource_loader|
|
78
|
+
raise Error, 'Invalid resource loader instance' unless resource_loader.instance_of?(GraphqlDevise::ResourceLoader)
|
79
|
+
|
80
|
+
resource_loader.call(@query, @mutation)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
GraphQL::Field.accepts_definitions(authenticate: GraphQL::Define.assign_metadata_key(:authenticate))
|
87
|
+
GraphQL::Schema::Field.accepts_definition(:authenticate)
|
@@ -3,10 +3,19 @@ module Api
|
|
3
3
|
class GraphqlController < ApplicationController
|
4
4
|
include GraphqlDevise::Concerns::SetUserByToken
|
5
5
|
|
6
|
-
before_action :
|
6
|
+
before_action -> { set_resource_by_token(:user) }
|
7
7
|
|
8
8
|
def graphql
|
9
|
-
render json: DummySchema.execute(params[:query])
|
9
|
+
render json: DummySchema.execute(params[:query], context: graphql_context)
|
10
|
+
end
|
11
|
+
|
12
|
+
def interpreter
|
13
|
+
render json: InterpreterSchema.execute(params[:query], context: graphql_context)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def verify_authenticity_token
|
10
19
|
end
|
11
20
|
end
|
12
21
|
end
|
@@ -1,4 +1,13 @@
|
|
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('User', only: [:login, :confirm_account]),
|
7
|
+
GraphqlDevise::ResourceLoader.new('Guest', only: [:logout])
|
8
|
+
]
|
9
|
+
)
|
10
|
+
|
2
11
|
mutation(Types::MutationType)
|
3
12
|
query(Types::QueryType)
|
4
13
|
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
|
@@ -10,7 +10,7 @@ Rails.application.configure do
|
|
10
10
|
# Do not eager load code on boot. This avoids loading your whole application
|
11
11
|
# just for the purpose of running a single test. If you are using a tool that
|
12
12
|
# preloads Rails for running tests, you may have to set it to true.
|
13
|
-
config.eager_load =
|
13
|
+
config.eager_load = ENV['EAGER_LOAD'].present?
|
14
14
|
|
15
15
|
# Configure public file server for tests with Cache-Control for performance.
|
16
16
|
if Rails::VERSION::MAJOR >= 5
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -3,50 +3,82 @@ require 'rails_helper'
|
|
3
3
|
require 'generators/graphql_devise/install_generator'
|
4
4
|
|
5
5
|
RSpec.describe GraphqlDevise::InstallGenerator, type: :generator do
|
6
|
-
destination File.expand_path('
|
6
|
+
destination File.expand_path('../../../../gqld_dummy', __dir__)
|
7
|
+
|
8
|
+
let(:routes_path) { "#{destination_root}/config/routes.rb" }
|
9
|
+
let(:routes_content) { File.read(routes_path) }
|
10
|
+
let(:dta_route) { 'mount_devise_token_auth_for' }
|
11
|
+
|
12
|
+
after(:all) { FileUtils.rm_rf(destination_root) }
|
7
13
|
|
8
14
|
before do
|
9
15
|
prepare_destination
|
16
|
+
create_rails_project
|
17
|
+
run_generator(args)
|
10
18
|
end
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
)
|
22
|
-
end
|
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'
|
23
29
|
|
24
|
-
|
25
|
-
before { run_generator }
|
30
|
+
assert_file 'app/models/admin.rb', /^\s{2}devise :.+include GraphqlDevise::Concerns::Model/m
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
expect(routes_content).not_to match(dta_route)
|
31
|
-
end
|
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')")}/
|
32
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when passing no params to the generator' do
|
39
|
+
let(:args) { [] }
|
33
40
|
|
34
|
-
|
35
|
-
|
41
|
+
it 'creates and updated required files' do
|
42
|
+
assert_file 'config/routes.rb', /^\s{2}mount_graphql_devise_for 'User', at: 'auth'/
|
43
|
+
expect(routes_content).not_to match(dta_route)
|
36
44
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
45
|
+
assert_file 'config/initializers/devise.rb'
|
46
|
+
assert_file 'config/initializers/devise_token_auth.rb', /^\s{2}#{Regexp.escape('config.change_headers_on_each_request = false')}/
|
47
|
+
assert_file 'config/locales/devise.en.yml'
|
48
|
+
|
49
|
+
assert_migration 'db/migrate/devise_token_auth_create_users.rb'
|
50
|
+
|
51
|
+
assert_file 'app/models/user.rb', /^\s{2}devise :.+include GraphqlDevise::Concerns::Model/m
|
52
|
+
|
53
|
+
assert_file 'app/controllers/application_controller.rb', /^\s{2}include GraphqlDevise::Concerns::SetUserByToken/
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
|
-
|
46
|
-
|
57
|
+
context 'when passing custom params to the generator' do
|
58
|
+
let(:args) { %w[Admin api] }
|
59
|
+
|
60
|
+
it 'creates and updated required files' do
|
61
|
+
assert_file 'config/routes.rb', /^\s{2}mount_graphql_devise_for 'Admin', at: 'api'/
|
62
|
+
expect(routes_content).not_to match(dta_route)
|
63
|
+
|
64
|
+
assert_file 'config/initializers/devise.rb'
|
65
|
+
assert_file 'config/initializers/devise_token_auth.rb', /^\s{2}#{Regexp.escape('config.change_headers_on_each_request = false')}/
|
66
|
+
assert_file 'config/locales/devise.en.yml'
|
67
|
+
|
68
|
+
assert_migration 'db/migrate/devise_token_auth_create_admins.rb'
|
47
69
|
|
48
|
-
|
49
|
-
|
70
|
+
assert_file 'app/models/admin.rb', /^\s{2}devise :.+include GraphqlDevise::Concerns::Model/m
|
71
|
+
|
72
|
+
assert_file 'app/controllers/application_controller.rb', /^\s{2}include GraphqlDevise::Concerns::SetUserByToken/
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_rails_project
|
77
|
+
FileUtils.cd(File.join(destination_root, '..')) do
|
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`
|
79
|
+
end
|
80
|
+
FileUtils.cd(File.join(destination_root, '../gqld_dummy')) do
|
81
|
+
`rails generate graphql:install`
|
50
82
|
end
|
51
83
|
end
|
52
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
|