Skip to content

Commit 75a48dd

Browse files
committed
Support skipping operations when mounting routes
1 parent 292209c commit 75a48dd

4 files changed

Lines changed: 176 additions & 113 deletions

File tree

lib/graphql_devise/rails/routes.rb

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,25 @@
11
module ActionDispatch::Routing
22
class Mapper
33
def mount_graphql_devise_for(resource, opts = {})
4-
custom_operations = opts[:operations] || {}
4+
custom_operations = opts[:operations] || {}
5+
skipped_operations = opts.fetch(:skip, [])
6+
7+
default_mutations = {
8+
login: GraphqlDevise::Mutations::Login,
9+
logout: GraphqlDevise::Mutations::Logout,
10+
sign_up: GraphqlDevise::Mutations::SignUp,
11+
update_password: GraphqlDevise::Mutations::UpdatePassword,
12+
send_reset_password: GraphqlDevise::Mutations::SendPasswordReset
13+
}.freeze
14+
default_queries = {
15+
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
16+
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
17+
}
18+
supported_operations = default_mutations.keys + default_queries.keys
19+
20+
unless skipped_operations.all? { |skipped| supported_operations.include?(skipped) }
21+
raise GraphqlDevise::Error, 'Trying to skip a non supported operation. Check for typos.'
22+
end
523

624
path = opts.fetch(:at, '/graphql_auth')
725
mapping_name = resource.underscore.tr('/', '_').to_sym
@@ -16,15 +34,7 @@ def mount_graphql_devise_for(resource, opts = {})
1634
"Types::#{resource}Type".safe_constantize ||
1735
GraphqlDevise::Types::AuthenticableType
1836

19-
default_mutations = {
20-
login: GraphqlDevise::Mutations::Login,
21-
logout: GraphqlDevise::Mutations::Logout,
22-
sign_up: GraphqlDevise::Mutations::SignUp,
23-
update_password: GraphqlDevise::Mutations::UpdatePassword,
24-
send_reset_password: GraphqlDevise::Mutations::SendPasswordReset
25-
}.freeze
26-
27-
default_mutations.each do |action, mutation|
37+
default_mutations.except(*skipped_operations).each do |action, mutation|
2838
used_mutation = if custom_operations[action].present?
2939
custom_operations[action]
3040
else
@@ -39,12 +49,7 @@ def mount_graphql_devise_for(resource, opts = {})
3949
GraphqlDevise::Types::MutationType.field("#{mapping_name}_#{action}", mutation: used_mutation)
4050
end
4151

42-
default_queries = {
43-
confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
44-
check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
45-
}
46-
47-
default_queries.each do |action, query|
52+
default_queries.except(*skipped_operations).each do |action, query|
4853
used_query = if custom_operations[action].present?
4954
custom_operations[action]
5055
else

spec/dummy/config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
mount_graphql_devise_for(
88
'Admin',
99
authenticable_type: Types::CustomAdminType,
10+
skip: [:sign_up, :check_password_token],
1011
at: '/api/v1/admin/graphql_auth'
1112
)
1213

spec/requests/mutations/sign_up_spec.rb

Lines changed: 81 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,69 +7,99 @@
77
let(:password) { Faker::Internet.password }
88
let(:email) { Faker::Internet.email }
99
let(:redirect) { Faker::Internet.url }
10-
let(:query) do
11-
<<-GRAPHQL
12-
mutation {
13-
userSignUp(
14-
email: "#{email}"
15-
name: "#{name}"
16-
password: "#{password}"
17-
passwordConfirmation: "#{password}"
18-
confirmSuccessUrl: "#{redirect}"
19-
) {
20-
user {
21-
email
22-
name
10+
11+
context 'when using the user model' do
12+
let(:query) do
13+
<<-GRAPHQL
14+
mutation {
15+
userSignUp(
16+
email: "#{email}"
17+
name: "#{name}"
18+
password: "#{password}"
19+
passwordConfirmation: "#{password}"
20+
confirmSuccessUrl: "#{redirect}"
21+
) {
22+
user {
23+
email
24+
name
25+
}
2326
}
2427
}
25-
}
26-
GRAPHQL
27-
end
28+
GRAPHQL
29+
end
2830

29-
context 'when params are correct' do
30-
it 'creates a new resource that requires confirmation' do
31-
expect { post_request }.to(
32-
change(User, :count).by(1)
33-
.and(change(ActionMailer::Base.deliveries, :count).by(1))
34-
)
31+
context 'when params are correct' do
32+
it 'creates a new resource that requires confirmation' do
33+
expect { post_request }.to(
34+
change(User, :count).by(1)
35+
.and(change(ActionMailer::Base.deliveries, :count).by(1))
36+
)
3537

36-
user = User.last
38+
user = User.last
3739

38-
expect(user).not_to be_active_for_authentication
39-
expect(user.confirmed_at).to be_nil
40-
expect(user.valid_password?(password)).to be_truthy
41-
expect(json_response[:data][:userSignUp]).to include(
42-
user: {
43-
email: email,
44-
name: name
45-
}
46-
)
40+
expect(user).not_to be_active_for_authentication
41+
expect(user.confirmed_at).to be_nil
42+
expect(user).to be_valid_password(password)
43+
expect(json_response[:data][:userSignUp]).to include(
44+
user: {
45+
email: email,
46+
name: name
47+
}
48+
)
4749

48-
email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
49-
link = email.css('a').first
50+
email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
51+
link = email.css('a').first
5052

51-
expect do
52-
get link['href']
53-
user.reload
54-
end.to change { user.active_for_authentication? }.to(true)
53+
expect do
54+
get link['href']
55+
user.reload
56+
end.to change { user.active_for_authentication? }.to(true)
57+
end
5558
end
56-
end
5759

58-
context 'when required params are missing' do
59-
let(:email) { '' }
60+
context 'when required params are missing' do
61+
let(:email) { '' }
6062

61-
it 'does *NOT* create resource a resource nor send an email' do
62-
expect { post_request }.to(
63-
not_change(User, :count)
64-
.and(not_change(ActionMailer::Base.deliveries, :count))
65-
)
63+
it 'does *NOT* create resource a resource nor send an email' do
64+
expect { post_request }.to(
65+
not_change(User, :count)
66+
.and(not_change(ActionMailer::Base.deliveries, :count))
67+
)
6668

67-
expect(json_response[:data][:userSignUp]).to be_nil
68-
expect(json_response[:errors]).to containing_exactly(
69-
hash_including(
70-
message: "User couldn't be registered",
71-
extensions: { code: 'USER_ERROR', detailed_errors: ["Email can't be blank"] }
69+
expect(json_response[:data][:userSignUp]).to be_nil
70+
expect(json_response[:errors]).to containing_exactly(
71+
hash_including(
72+
message: "User couldn't be registered",
73+
extensions: { code: 'USER_ERROR', detailed_errors: ["Email can't be blank"] }
74+
)
7275
)
76+
end
77+
end
78+
end
79+
80+
context 'when using the admin model' do
81+
let(:query) do
82+
<<-GRAPHQL
83+
mutation {
84+
adminSignUp(
85+
email: "#{email}"
86+
password: "#{password}"
87+
passwordConfirmation: "#{password}"
88+
confirmSuccessUrl: "#{redirect}"
89+
) {
90+
authenticable {
91+
email
92+
}
93+
}
94+
}
95+
GRAPHQL
96+
end
97+
98+
before { post_request }
99+
100+
it 'skips the sign up mutation' do
101+
expect(json_response[:errors]).to contain_exactly(
102+
hash_including(message: "Field 'adminSignUp' doesn't exist on type 'Mutation'")
73103
)
74104
end
75105
end

spec/requests/queries/check_password_token_spec.rb

Lines changed: 73 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,76 +5,103 @@
55

66
let(:user) { create(:user, :confirmed) }
77
let(:redirect_url) { 'https://google.com' }
8-
let(:query) do
9-
<<-GRAPHQL
10-
query {
11-
userCheckPasswordToken(
12-
resetPasswordToken: "#{token}",
13-
redirectUrl: "#{redirect_url}"
14-
) {
15-
email
8+
9+
context 'when using the user model' do
10+
let(:query) do
11+
<<-GRAPHQL
12+
query {
13+
userCheckPasswordToken(
14+
resetPasswordToken: "#{token}",
15+
redirectUrl: "#{redirect_url}"
16+
) {
17+
email
18+
}
1619
}
17-
}
18-
GRAPHQL
19-
end
20+
GRAPHQL
21+
end
2022

21-
context 'when reset password token is valid' do
22-
let(:token) { user.send(:set_reset_password_token) }
23+
context 'when reset password token is valid' do
24+
let(:token) { user.send(:set_reset_password_token) }
2325

24-
context 'when redirect_url is not provided' do
25-
let(:redirect_url) { nil }
26+
context 'when redirect_url is not provided' do
27+
let(:redirect_url) { nil }
2628

27-
it 'returns authenticable and credentials in the headers' do
28-
get_request
29+
it 'returns authenticable and credentials in the headers' do
30+
get_request
2931

30-
expect(response).to include_auth_headers
31-
expect(json_response[:data][:userCheckPasswordToken]).to match(
32-
email: user.email
33-
)
32+
expect(response).to include_auth_headers
33+
expect(json_response[:data][:userCheckPasswordToken]).to match(
34+
email: user.email
35+
)
36+
end
3437
end
35-
end
3638

37-
context 'when redirect url is provided' do
38-
it 'redirects to redirect url' do
39-
expect do
40-
get_request
39+
context 'when redirect url is provided' do
40+
it 'redirects to redirect url' do
41+
expect do
42+
get_request
4143

42-
user.reload
43-
end.to change { user.tokens.keys.count }.from(0).to(1).and(
44-
change(user, :allow_password_change).from(false).to(true)
45-
)
44+
user.reload
45+
end.to change { user.tokens.keys.count }.from(0).to(1).and(
46+
change(user, :allow_password_change).from(false).to(true)
47+
)
4648

47-
expect(response).to redirect_to %r{\Ahttps://google.com}
48-
expect(response.body).to include("client=#{user.reload.tokens.keys.first}")
49-
expect(response.body).to include('access-token=')
50-
expect(response.body).to include('uid=')
51-
expect(response.body).to include('expiry=')
49+
expect(response).to redirect_to %r{\Ahttps://google.com}
50+
expect(response.body).to include("client=#{user.reload.tokens.keys.first}")
51+
expect(response.body).to include('access-token=')
52+
expect(response.body).to include('uid=')
53+
expect(response.body).to include('expiry=')
54+
end
5255
end
53-
end
5456

55-
context 'when token has expired' do
56-
it 'returns an expired token error' do
57-
travel_to 10.hours.ago do
58-
token
57+
context 'when token has expired' do
58+
it 'returns an expired token error' do
59+
travel_to 10.hours.ago do
60+
token
61+
end
62+
63+
get_request
64+
65+
expect(json_response[:errors]).to contain_exactly(
66+
hash_including(message: 'Reset password token is no longer valid.', extensions: { code: 'USER_ERROR' })
67+
)
5968
end
69+
end
70+
end
71+
72+
context 'when reset password token is not found' do
73+
let(:token) { user.send(:set_reset_password_token) + 'invalid' }
6074

75+
it 'redirects to redirect url' do
6176
get_request
6277

6378
expect(json_response[:errors]).to contain_exactly(
64-
hash_including(message: 'Reset password token is no longer valid.', extensions: { code: 'USER_ERROR' })
79+
hash_including(message: 'No user found for the specified reset token.', extensions: { code: 'USER_ERROR' })
6580
)
6681
end
6782
end
6883
end
6984

70-
context 'when reset password token is not found' do
71-
let(:token) { user.send(:set_reset_password_token) + 'invalid' }
85+
context 'when using the admin model' do
86+
let(:token) { 'not_important' }
87+
let(:query) do
88+
<<-GRAPHQL
89+
query {
90+
adminCheckPasswordToken(
91+
resetPasswordToken: "#{token}",
92+
redirectUrl: "#{redirect_url}"
93+
) {
94+
email
95+
}
96+
}
97+
GRAPHQL
98+
end
7299

73-
it 'redirects to redirect url' do
74-
get_request
100+
before { post_request }
75101

102+
it 'skips the sign up mutation' do
76103
expect(json_response[:errors]).to contain_exactly(
77-
hash_including(message: 'No user found for the specified reset token.', extensions: { code: 'USER_ERROR' })
104+
hash_including(message: "Field 'adminCheckPasswordToken' doesn't exist on type 'Query'")
78105
)
79106
end
80107
end

0 commit comments

Comments
 (0)