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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +7 -0
  4. data/Appraisals +14 -0
  5. data/CHANGELOG.md +48 -1
  6. data/README.md +181 -20
  7. data/app/controllers/graphql_devise/application_controller.rb +4 -1
  8. data/app/controllers/graphql_devise/concerns/set_user_by_token.rb +25 -0
  9. data/app/controllers/graphql_devise/graphql_controller.rb +2 -0
  10. data/app/helpers/graphql_devise/mailer_helper.rb +2 -2
  11. data/app/views/graphql_devise/mailer/confirmation_instructions.html.erb +1 -1
  12. data/app/views/graphql_devise/mailer/reset_password_instructions.html.erb +1 -1
  13. data/config/locales/en.yml +1 -0
  14. data/config/routes.rb +2 -0
  15. data/graphql_devise.gemspec +6 -4
  16. data/lib/generators/graphql_devise/install_generator.rb +28 -5
  17. data/lib/graphql_devise.rb +24 -10
  18. data/lib/graphql_devise/default_operations/mutations.rb +6 -6
  19. data/lib/graphql_devise/default_operations/resolvers.rb +2 -2
  20. data/lib/graphql_devise/errors/authentication_error.rb +7 -0
  21. data/lib/graphql_devise/{detailed_user_error.rb → errors/detailed_user_error.rb} +1 -1
  22. data/lib/graphql_devise/errors/error_codes.rb +6 -0
  23. data/lib/graphql_devise/errors/execution_error.rb +4 -0
  24. data/lib/graphql_devise/{user_error.rb → errors/user_error.rb} +1 -1
  25. data/lib/graphql_devise/mount_method/operation_preparer.rb +2 -2
  26. data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +6 -2
  27. data/lib/graphql_devise/mount_method/operation_preparers/gql_name_setter.rb +1 -1
  28. data/lib/graphql_devise/mount_method/operation_preparers/mutation_field_setter.rb +3 -2
  29. data/lib/graphql_devise/mount_method/operation_preparers/resolver_type_setter.rb +1 -1
  30. data/lib/graphql_devise/mount_method/operation_preparers/resource_name_setter.rb +2 -2
  31. data/lib/graphql_devise/mutations/resend_confirmation.rb +3 -5
  32. data/lib/graphql_devise/mutations/send_password_reset.rb +5 -2
  33. data/lib/graphql_devise/mutations/sign_up.rb +3 -6
  34. data/lib/graphql_devise/rails/routes.rb +5 -72
  35. data/lib/graphql_devise/resource_loader.rb +87 -0
  36. data/lib/graphql_devise/schema_plugin.rb +106 -0
  37. data/lib/graphql_devise/version.rb +1 -1
  38. data/spec/dummy/app/controllers/api/v1/graphql_controller.rb +41 -3
  39. data/spec/dummy/app/controllers/application_controller.rb +1 -0
  40. data/spec/dummy/app/graphql/dummy_schema.rb +18 -0
  41. data/spec/dummy/app/graphql/interpreter_schema.rb +9 -0
  42. data/spec/dummy/app/graphql/types/mutation_type.rb +1 -1
  43. data/spec/dummy/app/graphql/types/query_type.rb +10 -0
  44. data/spec/dummy/config/routes.rb +3 -0
  45. data/spec/generators/graphql_devise/install_generator_spec.rb +21 -0
  46. data/spec/rails_helper.rb +4 -1
  47. data/spec/requests/graphql_controller_spec.rb +80 -0
  48. data/spec/requests/mutations/resend_confirmation_spec.rb +44 -29
  49. data/spec/requests/mutations/send_password_reset_spec.rb +40 -12
  50. data/spec/requests/queries/confirm_account_spec.rb +7 -1
  51. data/spec/requests/user_controller_spec.rb +189 -24
  52. data/spec/services/mount_method/operation_preparer_spec.rb +8 -3
  53. data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +1 -1
  54. data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +15 -8
  55. data/spec/services/mount_method/operation_preparers/mutation_field_setter_spec.rb +18 -4
  56. data/spec/services/mount_method/operation_preparers/resource_name_setter_spec.rb +1 -1
  57. data/spec/services/resource_loader_spec.rb +82 -0
  58. data/spec/services/schema_plugin_spec.rb +26 -0
  59. metadata +107 -87
  60. data/lib/graphql_devise/error_codes.rb +0 -5
  61. data/spec/support/generators/file_helpers.rb +0 -12
@@ -15,39 +15,58 @@ RSpec.describe 'Resend confirmation' do
15
15
  redirectUrl:"#{redirect}"
16
16
  ) {
17
17
  message
18
- authenticatable {
19
- id
20
- email
21
- }
22
18
  }
23
19
  }
24
20
  GRAPHQL
25
21
  end
26
22
 
27
23
  context 'when params are correct' do
28
- it 'sends an email to the user with confirmation url and returns a success message' do
29
- expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
30
- expect(json_response[:data][:userResendConfirmation]).to include(
31
- message: 'You will receive an email with instructions for how to confirm your email address in a few minutes.',
32
- authenticatable: {
33
- id: id,
34
- email: email
35
- }
36
- )
24
+ context 'when using the gem schema' do
25
+ it 'sends an email to the user with confirmation url and returns a success message' do
26
+ expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
27
+ expect(json_response[:data][:userResendConfirmation]).to include(
28
+ message: 'You will receive an email with instructions for how to confirm your email address in a few minutes.'
29
+ )
30
+
31
+ email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
32
+ link = email.css('a').first
33
+ confirm_link_msg_text = email.css('p')[1].inner_html
34
+ confirm_account_link_text = link.inner_html
37
35
 
38
- email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
39
- link = email.css('a').first
40
- confirm_link_msg_text = email.css('p')[1].inner_html
41
- confirm_account_link_text = link.inner_html
36
+ expect(link['href']).to include('/api/v1/graphql_auth?')
37
+ expect(confirm_link_msg_text).to eq('You can confirm your account email through the link below:')
38
+ expect(confirm_account_link_text).to eq('Confirm my account')
42
39
 
43
- expect(confirm_link_msg_text).to eq('You can confirm your account email through the link below:')
44
- expect(confirm_account_link_text).to eq('Confirm my account')
40
+ expect do
41
+ get link['href']
42
+ user.reload
43
+ end.to change(user, :confirmed_at).from(NilClass).to(ActiveSupport::TimeWithZone)
44
+ end
45
+ end
45
46
 
46
- # TODO: Move to feature spec
47
- expect do
48
- get link['href']
49
- user.reload
50
- end.to change(user, :confirmed_at).from(NilClass).to(ActiveSupport::TimeWithZone)
47
+ context 'when using a custom schema' do
48
+ let(:custom_path) { '/api/v1/graphql' }
49
+
50
+ it 'sends an email to the user with confirmation url and returns a success message' do
51
+ expect { post_request(custom_path) }.to change(ActionMailer::Base.deliveries, :count).by(1)
52
+ expect(json_response[:data][:userResendConfirmation]).to include(
53
+ message: 'You will receive an email with instructions for how to confirm your email address in a few minutes.'
54
+ )
55
+
56
+ email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
57
+ link = email.css('a').first
58
+ confirm_link_msg_text = email.css('p')[1].inner_html
59
+ confirm_account_link_text = link.inner_html
60
+
61
+ expect(link['href']).to include("#{custom_path}?")
62
+ expect(confirm_link_msg_text).to eq('You can confirm your account email through the link below:')
63
+ expect(confirm_account_link_text).to eq('Confirm my account')
64
+
65
+ expect do
66
+ get link['href']
67
+ user.reload
68
+ end.to change(user, :confirmed_at).from(NilClass).to(ActiveSupport::TimeWithZone)
69
+ end
51
70
  end
52
71
 
53
72
  context 'when email address uses different casing' do
@@ -56,11 +75,7 @@ RSpec.describe 'Resend confirmation' do
56
75
  it 'honors devise configuration for case insensitive fields' do
57
76
  expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
58
77
  expect(json_response[:data][:userResendConfirmation]).to include(
59
- message: 'You will receive an email with instructions for how to confirm your email address in a few minutes.',
60
- authenticatable: {
61
- id: id,
62
- email: user.email
63
- }
78
+ message: 'You will receive an email with instructions for how to confirm your email address in a few minutes.'
64
79
  )
65
80
  end
66
81
  end
@@ -13,26 +13,51 @@ RSpec.describe 'Send Password Reset Requests' do
13
13
  email: "#{email}",
14
14
  redirectUrl: "#{redirect_url}"
15
15
  ) {
16
- authenticatable {
17
- email
18
- }
16
+ message
19
17
  }
20
18
  }
21
19
  GRAPHQL
22
20
  end
23
21
 
24
22
  context 'when params are correct' do
25
- it 'sends password reset email' do
26
- expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
23
+ context 'when using the gem schema' do
24
+ it 'sends password reset email' do
25
+ expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
26
+
27
+ expect(json_response[:data][:userSendPasswordReset]).to include(
28
+ message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
29
+ )
27
30
 
28
- email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
29
- link = email.css('a').first
31
+ email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
32
+ link = email.css('a').first
33
+ expect(link['href']).to include('/api/v1/graphql_auth?')
30
34
 
31
- # TODO: Move to feature spec
32
- expect do
33
- get link['href']
34
- user.reload
35
- end.to change(user, :allow_password_change).from(false).to(true)
35
+ expect do
36
+ get link['href']
37
+ user.reload
38
+ end.to change(user, :allow_password_change).from(false).to(true)
39
+ end
40
+ end
41
+
42
+ context 'when using a custom schema' do
43
+ let(:custom_path) { '/api/v1/graphql' }
44
+
45
+ it 'sends password reset email' do
46
+ expect { post_request(custom_path) }.to change(ActionMailer::Base.deliveries, :count).by(1)
47
+
48
+ expect(json_response[:data][:userSendPasswordReset]).to include(
49
+ message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
50
+ )
51
+
52
+ email = Nokogiri::HTML(ActionMailer::Base.deliveries.last.body.encoded)
53
+ link = email.css('a').first
54
+ expect(link['href']).to include("#{custom_path}?")
55
+
56
+ expect do
57
+ get link['href']
58
+ user.reload
59
+ end.to change(user, :allow_password_change).from(false).to(true)
60
+ end
36
61
  end
37
62
  end
38
63
 
@@ -41,6 +66,9 @@ RSpec.describe 'Send Password Reset Requests' do
41
66
 
42
67
  it 'honors devise configuration for case insensitive fields' do
43
68
  expect { post_request }.to change(ActionMailer::Base.deliveries, :count).by(1)
69
+ expect(json_response[:data][:userSendPasswordReset]).to include(
70
+ message: 'You will receive an email with instructions on how to reset your password in a few minutes.'
71
+ )
44
72
  end
45
73
  end
46
74
 
@@ -22,7 +22,13 @@ RSpec.describe 'Account confirmation' do
22
22
  context 'when confirmation token is correct' do
23
23
  let(:token) { user.confirmation_token }
24
24
 
25
- before { user.send_confirmation_instructions(template_path: ['graphql_devise/mailer']) }
25
+ before do
26
+ user.send_confirmation_instructions(
27
+ template_path: ['graphql_devise/mailer'],
28
+ controller: 'graphql_devise/graphql',
29
+ action: 'auth'
30
+ )
31
+ end
26
32
 
27
33
  it 'confirms the resource and redirects to the sent url' do
28
34
  expect do
@@ -1,40 +1,205 @@
1
1
  require 'rails_helper'
2
2
 
3
- RSpec.describe 'Integrations with the user controller' do
3
+ RSpec.describe "Integrations with the user's controller" do
4
4
  include_context 'with graphql query request'
5
5
 
6
6
  let(:user) { create(:user, :confirmed) }
7
- let(:query) do
8
- <<-GRAPHQL
9
- query {
10
- user(
11
- id: #{user.id}
12
- ) {
13
- id
14
- email
7
+
8
+ describe 'publicField' do
9
+ let(:query) do
10
+ <<-GRAPHQL
11
+ query {
12
+ publicField
13
+ }
14
+ GRAPHQL
15
+ end
16
+
17
+ context 'when using a regular schema' do
18
+ before { post_request('/api/v1/graphql') }
19
+
20
+ it 'does not require authentication' do
21
+ expect(json_response[:data][:publicField]).to eq('Field does not require authentication')
22
+ end
23
+ end
24
+
25
+ context 'when using an interpreter schema' do
26
+ before { post_request('/api/v1/interpreter') }
27
+
28
+ it 'does not require authentication' do
29
+ expect(json_response[:data][:publicField]).to eq('Field does not require authentication')
30
+ end
31
+ end
32
+
33
+ context 'when using the failing route' do
34
+ it 'raises an invalid resource_name error' do
35
+ expect { post_request('/api/v1/failing') }.to raise_error(
36
+ GraphqlDevise::Error,
37
+ 'Invalid resource_name `fail` provided to `graphql_context`. Possible values are: [:user, :admin, :guest, :users_customer].'
38
+ )
39
+ end
40
+ end
41
+ end
42
+
43
+ describe 'privateField' do
44
+ let(:query) do
45
+ <<-GRAPHQL
46
+ query {
47
+ privateField
15
48
  }
16
- }
17
- GRAPHQL
49
+ GRAPHQL
50
+ end
51
+
52
+ context 'when using a regular schema' do
53
+ before { post_request('/api/v1/graphql') }
54
+
55
+ context 'when user is authenticated' do
56
+ let(:headers) { user.create_new_auth_token }
57
+
58
+ it 'allow to perform the query' do
59
+ expect(json_response[:data][:privateField]).to eq('Field will always require authentication')
60
+ end
61
+ end
62
+
63
+ context 'when user is not authenticated' do
64
+ it 'returns a must sign in error' do
65
+ expect(json_response[:errors]).to contain_exactly(
66
+ hash_including(message: 'privateField field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
67
+ )
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'when using an interpreter schema' do
73
+ before { post_request('/api/v1/interpreter') }
74
+
75
+ context 'when user is authenticated' do
76
+ let(:headers) { user.create_new_auth_token }
77
+
78
+ it 'allow to perform the query' do
79
+ expect(json_response[:data][:privateField]).to eq('Field will always require authentication')
80
+ end
81
+ end
82
+
83
+ context 'when user is not authenticated' do
84
+ it 'returns a must sign in error' do
85
+ expect(json_response[:errors]).to contain_exactly(
86
+ hash_including(message: 'privateField field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
87
+ )
88
+ end
89
+ end
90
+ end
18
91
  end
19
92
 
20
- before { post_request('/api/v1/graphql') }
93
+ describe 'dummyMutation' do
94
+ let(:query) do
95
+ <<-GRAPHQL
96
+ mutation {
97
+ dummyMutation
98
+ }
99
+ GRAPHQL
100
+ end
101
+
102
+ context 'when using a regular schema' do
103
+ before { post_request('/api/v1/graphql') }
104
+
105
+ context 'when user is authenticated' do
106
+ let(:headers) { user.create_new_auth_token }
107
+
108
+ it 'allow to perform the query' do
109
+ expect(json_response[:data][:dummyMutation]).to eq('Necessary so GraphQL gem does not complain about empty mutation type')
110
+ end
111
+ end
112
+
113
+ context 'when user is not authenticated' do
114
+ it 'returns a must sign in error' do
115
+ expect(json_response[:errors]).to contain_exactly(
116
+ hash_including(message: 'dummyMutation field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
117
+ )
118
+ end
119
+ end
120
+ end
121
+
122
+ context 'when using an interpreter schema' do
123
+ before { post_request('/api/v1/interpreter') }
124
+
125
+ context 'when user is authenticated' do
126
+ let(:headers) { user.create_new_auth_token }
21
127
 
22
- context 'when user is authenticated' do
23
- let(:headers) { user.create_new_auth_token }
128
+ it 'allow to perform the query' do
129
+ expect(json_response[:data][:dummyMutation]).to eq('Necessary so GraphQL gem does not complain about empty mutation type')
130
+ end
131
+ end
24
132
 
25
- it 'allow to perform the query' do
26
- expect(json_response[:data][:user]).to match(
27
- email: user.email,
28
- id: user.id
29
- )
133
+ context 'when user is not authenticated' do
134
+ it 'returns a must sign in error' do
135
+ expect(json_response[:errors]).to contain_exactly(
136
+ hash_including(message: 'dummyMutation field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
137
+ )
138
+ end
139
+ end
30
140
  end
31
141
  end
32
142
 
33
- context 'when user is not authenticated' do
34
- it 'returns a must sign in error' do
35
- expect(json_response[:errors]).to contain_exactly(
36
- 'You need to sign in or sign up before continuing.'
37
- )
143
+ describe 'user' do
144
+ let(:query) do
145
+ <<-GRAPHQL
146
+ query {
147
+ user(
148
+ id: #{user.id}
149
+ ) {
150
+ id
151
+ email
152
+ }
153
+ }
154
+ GRAPHQL
155
+ end
156
+
157
+ context 'when using a regular schema' do
158
+ before { post_request('/api/v1/graphql') }
159
+
160
+ context 'when user is authenticated' do
161
+ let(:headers) { user.create_new_auth_token }
162
+
163
+ it 'allow to perform the query' do
164
+ expect(json_response[:data][:user]).to match(
165
+ email: user.email,
166
+ id: user.id
167
+ )
168
+ end
169
+ end
170
+
171
+ context 'when user is not authenticated' do
172
+ it 'returns a must sign in error' do
173
+ expect(json_response[:errors]).to contain_exactly(
174
+ hash_including(message: 'user field requires authentication', extensions: { code: 'AUTHENTICATION_ERROR' })
175
+ )
176
+ end
177
+ end
178
+ end
179
+
180
+ context 'when using an interpreter schema' do
181
+ before { post_request('/api/v1/interpreter') }
182
+
183
+ context 'when user is authenticated' do
184
+ let(:headers) { user.create_new_auth_token }
185
+
186
+ it 'allow to perform the query' do
187
+ expect(json_response[:data][:user]).to match(
188
+ email: user.email,
189
+ id: user.id
190
+ )
191
+ end
192
+ end
193
+
194
+ context 'when user is not authenticated' do
195
+ # Interpreter schema fields are public unless specified otherwise (plugin setting)
196
+ it 'allow to perform the query' do
197
+ expect(json_response[:data][:user]).to match(
198
+ email: user.email,
199
+ id: user.id
200
+ )
201
+ end
202
+ end
38
203
  end
39
204
  end
40
205
  end
@@ -4,7 +4,7 @@ RSpec.describe GraphqlDevise::MountMethod::OperationPreparer do
4
4
  describe '#call' do
5
5
  subject(:prepared_operations) do
6
6
  described_class.new(
7
- resource: resource,
7
+ mapping_name: mapping,
8
8
  selected_operations: selected,
9
9
  preparer: preparer,
10
10
  custom: custom,
@@ -13,11 +13,16 @@ RSpec.describe GraphqlDevise::MountMethod::OperationPreparer do
13
13
  end
14
14
 
15
15
  let(:logout_class) { Class.new(GraphQL::Schema::Resolver) }
16
- let(:resource) { 'User' }
17
- let(:selected) { { login: double(:login_default), logout: logout_class } }
16
+ let(:mapping) { :user }
18
17
  let(:preparer) { double(:preparer, call: logout_class) }
19
18
  let(:custom) { { login: double(:custom_login, graphql_name: nil) } }
20
19
  let(:additional) { { user_additional: double(:user_additional) } }
20
+ let(:selected) do
21
+ {
22
+ login: { klass: double(:login_default) },
23
+ logout:{ klass: logout_class }
24
+ }
25
+ end
21
26
 
22
27
  it 'is expected to return all provided operation keys' do
23
28
  expect(prepared_operations.keys).to contain_exactly(
@@ -6,7 +6,7 @@ RSpec.describe GraphqlDevise::MountMethod::OperationPreparers::CustomOperationPr
6
6
 
7
7
  let(:login_operation) { double(:confirm_operation, graphql_name: nil) }
8
8
  let(:logout_operation) { double(:sign_up_operation, graphql_name: nil) }
9
- let(:mapping_name) { 'user' }
9
+ let(:mapping_name) { :user }
10
10
  let(:operations) { { login: login_operation, logout: logout_operation, invalid: double(:invalid) } }
11
11
  let(:selected_keys) { [:login, :logout, :sign_up, :confirm] }
12
12