jsonapi.rb 1.5.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe NotesController, type: :request do
4
+ describe 'PUT /notes/:id' do
5
+ let(:note) { create_note }
6
+ let(:note_id) { note.id }
7
+ let(:user) { note.user }
8
+ let(:user_id) { user.id }
9
+ let(:note_params) do
10
+ {
11
+ data: {
12
+ attributes: { title: FFaker::Company.name },
13
+ relationships: { user: { data: { id: user_id } } }
14
+ }
15
+ }
16
+ end
17
+ let(:params) { note_params }
18
+
19
+ before do
20
+ put(note_path(note_id), params: params.to_json, headers: jsonapi_headers)
21
+ end
22
+
23
+ it do
24
+ expect(response).to have_http_status(:ok)
25
+ expect(response_json['data']).to have_id(note.id.to_s)
26
+ expect(response_json['meta']).to eq('single' => true)
27
+ end
28
+
29
+ context 'with a missing parameter in the payload' do
30
+ let(:params) { {} }
31
+
32
+ it do
33
+ expect(response).to have_http_status(:unprocessable_entity)
34
+ expect(response_json['errors'].size).to eq(1)
35
+ expect(response_json['errors'][0]['status']).to eq('422')
36
+ expect(response_json['errors'][0]['title'])
37
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[422])
38
+ expect(response_json['errors'][0]['source']).to eq('pointer' => '')
39
+ expect(response_json['errors'][0]['detail']).to be_nil
40
+ end
41
+ end
42
+
43
+ context 'with an invalid payload' do
44
+ let(:params) do
45
+ payload = note_params.dup
46
+ payload[:data][:relationships][:user][:data][:id] = nil
47
+ payload
48
+ end
49
+
50
+ it do
51
+ expect(response).to have_http_status(:unprocessable_entity)
52
+ expect(response_json['errors'].size).to eq(1)
53
+ expect(response_json['errors'][0]['status']).to eq('422')
54
+ expect(response_json['errors'][0]['code']).to include('blank')
55
+ expect(response_json['errors'][0]['title'])
56
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[422])
57
+ expect(response_json['errors'][0]['source'])
58
+ .to eq('pointer' => '/data/relationships/user')
59
+ if Rails.gem_version >= Gem::Version.new('6.1')
60
+ expect(response_json['errors'][0]['detail'])
61
+ .to eq('User must exist')
62
+ else
63
+ expect(response_json['errors'][0]['detail'])
64
+ .to eq('User can\'t be blank')
65
+ end
66
+ end
67
+
68
+ context 'required by validations' do
69
+ let(:params) do
70
+ payload = note_params.dup
71
+ payload[:data][:attributes][:title] = 'BAD_TITLE'
72
+ payload[:data][:attributes][:quantity] = 100 + rand(10)
73
+ payload
74
+ end
75
+
76
+ it do
77
+ expect(response).to have_http_status(:unprocessable_entity)
78
+ expect(response_json['errors'].size).to eq(3)
79
+ expect(response_json['errors'][0]['status']).to eq('422')
80
+ expect(response_json['errors'][0]['code']).to include('invalid')
81
+ expect(response_json['errors'][0]['title'])
82
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[422])
83
+ expect(response_json['errors'][0]['source'])
84
+ .to eq('pointer' => '/data/attributes/title')
85
+ expect(response_json['errors'][0]['detail'])
86
+ .to eq('Title is invalid')
87
+
88
+ expect(response_json['errors'][1]['status']).to eq('422')
89
+
90
+ if Rails::VERSION::MAJOR >= 5
91
+ expect(response_json['errors'][1]['code']).to eq('invalid')
92
+ else
93
+ expect(response_json['errors'][1]['code']).to eq('has_typos')
94
+ end
95
+
96
+ expect(response_json['errors'][1]['title'])
97
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[422])
98
+ expect(response_json['errors'][1]['source'])
99
+ .to eq('pointer' => '/data/attributes/title')
100
+ expect(response_json['errors'][1]['detail'])
101
+ .to eq('Title has typos')
102
+
103
+ expect(response_json['errors'][2]['status']).to eq('422')
104
+
105
+ if Rails::VERSION::MAJOR >= 5
106
+ expect(response_json['errors'][2]['code']).to eq('less_than')
107
+ else
108
+ expect(response_json['errors'][2]['code'])
109
+ .to eq('must_be_less_than_100')
110
+ end
111
+
112
+ expect(response_json['errors'][2]['title'])
113
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[422])
114
+ expect(response_json['errors'][2]['source'])
115
+ .to eq('pointer' => '/data/attributes/quantity')
116
+ expect(response_json['errors'][2]['detail'])
117
+ .to eq('Quantity must be less than 100')
118
+ end
119
+ end
120
+
121
+ context 'as a param attribute' do
122
+ let(:params) do
123
+ payload = note_params.dup
124
+ payload[:data][:attributes].delete(:title)
125
+ # To have any attribtues in the payload...
126
+ payload[:data][:attributes][:created_at] = nil
127
+ payload
128
+ end
129
+
130
+ it do
131
+ expect(response).to have_http_status(:unprocessable_entity)
132
+ expect(response_json['errors'][0]['source'])
133
+ .to eq('pointer' => '/data/attributes/title')
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'with a bad note ID' do
139
+ let(:user_id) { nil }
140
+ let(:note_id) { rand(10) }
141
+
142
+ it do
143
+ expect(response).to have_http_status(:not_found)
144
+ expect(response_json['errors'].size).to eq(1)
145
+ expect(response_json['errors'][0]['status']).to eq('404')
146
+ expect(response_json['errors'][0]['title'])
147
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[404])
148
+ expect(response_json['errors'][0]['source']).to be_nil
149
+ expect(response_json['errors'][0]['detail']).to be_nil
150
+ end
151
+ end
152
+
153
+ context 'with an exception' do
154
+ let(:user_id) { nil }
155
+ let(:note_id) { 'tada' }
156
+
157
+ it do
158
+ expect(response).to have_http_status(:internal_server_error)
159
+ expect(response_json['errors'].size).to eq(1)
160
+ expect(response_json['errors'][0]['status']).to eq('500')
161
+ expect(response_json['errors'][0]['title'])
162
+ .to eq(Rack::Utils::HTTP_STATUS_CODES[500])
163
+ expect(response_json['errors'][0]['source']).to be_nil
164
+ expect(response_json['errors'][0]['detail']).to be_nil
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe 'GET /users' do
5
+ let!(:user) { }
6
+ let(:params) { }
7
+
8
+ before do
9
+ get(users_path, params: params, headers: jsonapi_headers)
10
+ end
11
+
12
+ context 'with users' do
13
+ let(:first_user) { create_user }
14
+ let(:second_user) { create_user }
15
+ let(:third_user) { create_note.user }
16
+ let(:users) { [first_user, second_user, third_user] }
17
+ let(:user) { users.last }
18
+ let(:note) { third_user.notes.first }
19
+
20
+ context 'returns customers and dasherized first name' do
21
+ let(:params) do
22
+ { upcase: :yes, fields: { unknown: nil } }
23
+ end
24
+
25
+ it do
26
+ expect(response).to have_http_status(:ok)
27
+ expect(response_json['data'].size).to eq(users.size)
28
+
29
+ response_json['data'].each do |item|
30
+ user = users.detect { |u| u.id == item['id'].to_i }
31
+ expect(item).to have_attribute('first_name')
32
+ .with_value(user.first_name.upcase)
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'returns customers included and sparse fields' do
38
+ let(:params) do
39
+ {
40
+ include: 'notes',
41
+ fields: { note: 'title,updated_at' }
42
+ }
43
+ end
44
+
45
+ it do
46
+ expect(response).to have_http_status(:ok)
47
+ expect(response_json['data'].last)
48
+ .to have_relationship(:notes)
49
+ .with_data([
50
+ { 'id' => note.id.to_s, 'type' => 'note' }
51
+ ])
52
+ expect(response_json['included']).to include(
53
+ 'id' => note.id.to_s,
54
+ 'type' => 'note',
55
+ 'relationships' => {},
56
+ 'attributes' => {
57
+ 'title' => note.title,
58
+ 'updated_at' => note.updated_at.as_json
59
+ }
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe '#extract_attributes_and_predicate' do
5
+ context 'mixed attributes (and/or)' do
6
+ it 'extracts ANDs' do
7
+ attributes, predicates = JSONAPI::Filtering
8
+ .extract_attributes_and_predicates('attr1_and_attr2_eq')
9
+ expect(attributes).to eq(['attr1', 'attr2'])
10
+ expect(predicates.size).to eq(1)
11
+ expect(predicates[0].name).to eq('eq')
12
+ end
13
+ end
14
+
15
+ context 'mixed predicates' do
16
+ it 'extracts in order' do
17
+ attributes, predicates = JSONAPI::Filtering
18
+ .extract_attributes_and_predicates('attr1_sum_eq')
19
+ expect(attributes).to eq(['attr1'])
20
+ expect(predicates.size).to eq(2)
21
+ expect(predicates[0].name).to eq('sum')
22
+ expect(predicates[1].name).to eq('eq')
23
+ end
24
+ end
25
+ end
26
+
27
+ describe 'GET /users' do
28
+ let!(:user) { }
29
+ let(:params) { }
30
+
31
+ before do
32
+ get(users_path, params: params, headers: jsonapi_headers)
33
+ end
34
+
35
+ context 'with users' do
36
+ let(:first_user) { create_user }
37
+ let(:second_user) { create_note.user }
38
+ let(:third_user) { create_user }
39
+ let(:users) { [first_user, second_user, third_user] }
40
+ let(:user) { users.last }
41
+
42
+ context 'returns filtered users' do
43
+ let(:params) do
44
+ {
45
+ filter: {
46
+ last_name_or_first_name_cont_any: (
47
+ "#{third_user.first_name[0..5]}%,#{self.class.name}"
48
+ )
49
+ }
50
+ }
51
+ end
52
+
53
+ it do
54
+ expect(response).to have_http_status(:ok)
55
+ expect(response_json['data'].size).to eq(1)
56
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
57
+ end
58
+
59
+ context 'with a comma' do
60
+ let(:params) do
61
+ third_user.update(first_name: third_user.first_name + ',')
62
+
63
+ {
64
+ filter: { first_name_eq: third_user.first_name }
65
+ }
66
+ end
67
+
68
+ it do
69
+ expect(response).to have_http_status(:ok)
70
+ expect(response_json['data'].size).to eq(1)
71
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
72
+ end
73
+ end
74
+ end
75
+
76
+ context 'returns sorted users by notes quantity sum' do
77
+ let(:params) do
78
+ { sort: '-notes_quantity_sum' }
79
+ end
80
+
81
+ it do
82
+ expect(response).to have_http_status(:ok)
83
+ expect(response_json['data'].size).to eq(3)
84
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
85
+ end
86
+ end
87
+
88
+ context 'returns sorted users by notes' do
89
+ let(:params) do
90
+ { sort: '-notes_created_at' }
91
+ end
92
+
93
+ it do
94
+ expect(response).to have_http_status(:ok)
95
+ expect(response_json['data'].size).to eq(3)
96
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,246 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe UsersController, type: :request do
4
+ describe 'GET /users' do
5
+ let!(:user) { }
6
+ let(:params) do
7
+ {
8
+ page: { number: 'Nan', size: 'NaN' },
9
+ sort: '-created_at'
10
+ }
11
+ end
12
+
13
+ before do
14
+ get(users_path, params: params, headers: jsonapi_headers)
15
+ end
16
+
17
+ it do
18
+ expect(response_json['data'].size).to eq(0)
19
+ expect(response_json['meta'])
20
+ .to eq(
21
+ 'many' => true,
22
+ 'pagination' => {
23
+ 'current' => 1,
24
+ 'records' => 0
25
+ }
26
+ )
27
+ end
28
+
29
+ context 'with users' do
30
+ let(:first_user) { create_user }
31
+ let(:second_user) { create_user }
32
+ let(:third_user) { create_user }
33
+ let(:users) { [first_user, second_user, third_user] }
34
+ let(:user) { users.last }
35
+
36
+ context 'returns users with pagination links' do
37
+ it do
38
+ expect(response).to have_http_status(:ok)
39
+ expect(response_json['data'].size).to eq(3)
40
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
41
+ expect(response_json['data'][1]).to have_id(second_user.id.to_s)
42
+ expect(response_json['data'][2]).to have_id(first_user.id.to_s)
43
+
44
+ expect(response_json).to have_link('self')
45
+ expect(response_json).not_to have_link(:prev)
46
+ expect(response_json).not_to have_link(:next)
47
+ expect(response_json).not_to have_link(:first)
48
+ expect(response_json).not_to have_link(:last)
49
+
50
+ expect(URI.parse(response_json['links']['self']).query)
51
+ .to eq(CGI.unescape(params.to_query))
52
+ end
53
+
54
+ context 'on page 2 out of 3' do
55
+ let(:as_list) { }
56
+ let(:params) do
57
+ {
58
+ page: { number: 2, size: 1 },
59
+ sort: '-created_at',
60
+ as_list: as_list
61
+ }.reject { |_k, _v| _v.blank? }
62
+ end
63
+
64
+ context 'on an array of resources' do
65
+ let(:as_list) { true }
66
+
67
+ it do
68
+ expect(response).to have_http_status(:ok)
69
+ expect(response_json['data'].size).to eq(1)
70
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
71
+
72
+ expect(response_json['meta']['pagination']).to eq(
73
+ 'current' => 2,
74
+ 'first' => 1,
75
+ 'prev' => 1,
76
+ 'next' => 3,
77
+ 'last' => 3,
78
+ 'records' => 3
79
+ )
80
+ end
81
+ end
82
+
83
+ it do
84
+ expect(response).to have_http_status(:ok)
85
+ expect(response_json['data'].size).to eq(1)
86
+ expect(response_json['data'][0]).to have_id(second_user.id.to_s)
87
+
88
+ expect(response_json['meta']['pagination']).to eq(
89
+ 'current' => 2,
90
+ 'first' => 1,
91
+ 'prev' => 1,
92
+ 'next' => 3,
93
+ 'last' => 3,
94
+ 'records' => 3
95
+ )
96
+
97
+ expect(response_json).to have_link(:self)
98
+ expect(response_json).to have_link(:prev)
99
+ expect(response_json).to have_link(:first)
100
+ expect(response_json).to have_link(:next)
101
+ expect(response_json).to have_link(:last)
102
+
103
+ qry = CGI.unescape(params.to_query)
104
+ expect(URI.parse(response_json['links']['self']).query).to eq(qry)
105
+
106
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
107
+ expect(URI.parse(response_json['links']['self']).query).to eq(qry)
108
+
109
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
110
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
111
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
112
+
113
+ qry = CGI.unescape(params.deep_merge(page: { number: 3 }).to_query)
114
+ expect(URI.parse(response_json['links']['next']).query).to eq(qry)
115
+ expect(URI.parse(response_json['links']['last']).query).to eq(qry)
116
+ end
117
+ end
118
+
119
+ context 'on page 3 out of 3' do
120
+ let(:params) do
121
+ {
122
+ page: { number: 3, size: 1 }
123
+ }
124
+ end
125
+
126
+ it do
127
+ expect(response).to have_http_status(:ok)
128
+ expect(response_json['data'].size).to eq(1)
129
+
130
+ expect(response_json['meta']['pagination']).to eq(
131
+ 'current' => 3,
132
+ 'first' => 1,
133
+ 'prev' => 2,
134
+ 'records' => 3
135
+ )
136
+
137
+ expect(response_json).to have_link(:self)
138
+ expect(response_json).to have_link(:prev)
139
+ expect(response_json).to have_link(:first)
140
+ expect(response_json).not_to have_link(:next)
141
+ expect(response_json).not_to have_link(:last)
142
+
143
+ expect(URI.parse(response_json['links']['self']).query)
144
+ .to eq(CGI.unescape(params.to_query))
145
+
146
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
147
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
148
+
149
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
150
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
151
+ end
152
+ end
153
+
154
+ context 'on paging beyond the last page' do
155
+ let(:as_list) { }
156
+ let(:params) do
157
+ {
158
+ page: { number: 5, size: 1 },
159
+ as_list: as_list
160
+ }.reject { |_k, _v| _v.blank? }
161
+ end
162
+
163
+ context 'on an array of resources' do
164
+ let(:as_list) { true }
165
+
166
+ it do
167
+ expect(response).to have_http_status(:ok)
168
+ expect(response_json['data'].size).to eq(0)
169
+
170
+ expect(response_json['meta']['pagination']).to eq(
171
+ 'current' => 5,
172
+ 'first' => 1,
173
+ 'prev' => 4,
174
+ 'records' => 3
175
+ )
176
+ end
177
+ end
178
+
179
+ it do
180
+ expect(response).to have_http_status(:ok)
181
+ expect(response_json['data'].size).to eq(0)
182
+
183
+ expect(response_json['meta']['pagination']).to eq(
184
+ 'current' => 5,
185
+ 'first' => 1,
186
+ 'prev' => 4,
187
+ 'records' => 3
188
+ )
189
+
190
+ expect(response_json).to have_link(:self)
191
+ expect(response_json).to have_link(:prev)
192
+ expect(response_json).to have_link(:first)
193
+ expect(response_json).not_to have_link(:next)
194
+ expect(response_json).not_to have_link(:last)
195
+
196
+ expect(URI.parse(response_json['links']['self']).query)
197
+ .to eq(CGI.unescape(params.to_query))
198
+
199
+ qry = CGI.unescape(params.deep_merge(page: { number: 4 }).to_query)
200
+ expect(URI.parse(response_json['links']['prev']).query).to eq(qry)
201
+
202
+ qry = CGI.unescape(params.deep_merge(page: { number: 1 }).to_query)
203
+ expect(URI.parse(response_json['links']['first']).query).to eq(qry)
204
+ end
205
+ end
206
+
207
+ context 'on page 1 out of 3' do
208
+ let(:params) do
209
+ {
210
+ page: { size: 1 },
211
+ sort: '-created_at'
212
+ }
213
+ end
214
+
215
+ it do
216
+ expect(response).to have_http_status(:ok)
217
+ expect(response_json['data'].size).to eq(1)
218
+ expect(response_json['data'][0]).to have_id(third_user.id.to_s)
219
+
220
+ expect(response_json['meta']['pagination']).to eq(
221
+ 'current' => 1,
222
+ 'next' => 2,
223
+ 'last' => 3,
224
+ 'records' => 3
225
+ )
226
+
227
+ expect(response_json).not_to have_link(:prev)
228
+ expect(response_json).not_to have_link(:first)
229
+ expect(response_json).to have_link(:next)
230
+ expect(response_json).to have_link(:self)
231
+ expect(response_json).to have_link(:last)
232
+
233
+ expect(URI.parse(response_json['links']['self']).query)
234
+ .to eq(CGI.unescape(params.to_query))
235
+
236
+ qry = CGI.unescape(params.deep_merge(page: { number: 2 }).to_query)
237
+ expect(URI.parse(response_json['links']['next']).query).to eq(qry)
238
+
239
+ qry = CGI.unescape(params.deep_merge(page: { number: 3 }).to_query)
240
+ expect(URI.parse(response_json['links']['last']).query).to eq(qry)
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,87 @@
1
+ require 'bundler/setup'
2
+ require 'simplecov'
3
+
4
+ SimpleCov.start do
5
+ add_group 'Lib', 'lib'
6
+ add_group 'Tests', 'spec'
7
+ end
8
+ SimpleCov.minimum_coverage 90
9
+
10
+ require 'dummy'
11
+ require 'ffaker'
12
+ require 'rspec/rails'
13
+ require 'jsonapi/rspec'
14
+
15
+ RSpec.configure do |config|
16
+ config.include JSONAPI::RSpec
17
+
18
+ config.use_transactional_fixtures = true
19
+ config.mock_with :rspec
20
+ config.filter_run_when_matching :focus
21
+ config.disable_monkey_patching!
22
+
23
+ config.expect_with :rspec do |c|
24
+ c.syntax = :expect
25
+ end
26
+ end
27
+
28
+ module RSpecHelpers
29
+ include Dummy.routes.url_helpers
30
+
31
+ # Helper to return JSONAPI valid headers
32
+ #
33
+ # @return [Hash] the relevant content type &co
34
+ def jsonapi_headers
35
+ { 'Content-Type': Mime[:jsonapi].to_s }
36
+ end
37
+
38
+ # Parses and returns a deserialized JSON
39
+ #
40
+ # @return [Hash]
41
+ def response_json
42
+ JSON.parse(response.body)
43
+ end
44
+
45
+ # Creates an user
46
+ #
47
+ # @return [User]
48
+ def create_user
49
+ User.create!(
50
+ first_name: FFaker::Name.first_name,
51
+ last_name: FFaker::Name.last_name
52
+ )
53
+ end
54
+
55
+ # Creates a note
56
+ #
57
+ # @return [Note]
58
+ def create_note(user = nil)
59
+ Note.create!(
60
+ title: FFaker::Company.name,
61
+ quantity: rand(10),
62
+ user: (user || create_user)
63
+ )
64
+ end
65
+ end
66
+
67
+ module Rails4RequestMethods
68
+ [:get, :post, :put, :delete].each do |method_name|
69
+ define_method(method_name) do |path, named_args|
70
+ super(
71
+ path,
72
+ named_args.delete(:params),
73
+ named_args.delete(:headers)
74
+ )
75
+ end
76
+ end
77
+ end
78
+
79
+ RSpec.configure do |config|
80
+ config.include RSpecHelpers, type: :request
81
+ config.include RSpecHelpers, type: :controller
82
+
83
+ if ::Rails::VERSION::MAJOR == 4
84
+ config.include Rails4RequestMethods, type: :request
85
+ config.include Rails4RequestMethods, type: :controller
86
+ end
87
+ end