infinum_json_api_setup 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.overcommit.yml +26 -0
- data/.rspec +2 -0
- data/.rubocop.yml +29 -0
- data/.ruby-version +1 -0
- data/.simplecov +3 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +268 -0
- data/README.md +156 -0
- data/Rakefile +1 -0
- data/bin/setup +18 -0
- data/infinum_json_api_setup.gemspec +30 -0
- data/lib/generators/infinum_json_api_setup/install_generator.rb +12 -0
- data/lib/generators/infinum_json_api_setup/templates/config/locales/json_api.en.yml +21 -0
- data/lib/infinum_json_api_setup/error.rb +61 -0
- data/lib/infinum_json_api_setup/json_api/content_negotiation.rb +29 -0
- data/lib/infinum_json_api_setup/json_api/error_handling.rb +49 -0
- data/lib/infinum_json_api_setup/json_api/error_serializer.rb +72 -0
- data/lib/infinum_json_api_setup/json_api/request_parsing.rb +17 -0
- data/lib/infinum_json_api_setup/json_api/responder.rb +31 -0
- data/lib/infinum_json_api_setup/json_api/serializer_options.rb +76 -0
- data/lib/infinum_json_api_setup/rails.rb +28 -0
- data/lib/infinum_json_api_setup/rspec/helpers/request_helper.rb +80 -0
- data/lib/infinum_json_api_setup/rspec/helpers/response_helper.rb +56 -0
- data/lib/infinum_json_api_setup/rspec/matchers/have_empty_data.rb +32 -0
- data/lib/infinum_json_api_setup/rspec/matchers/have_error_pointer.rb +37 -0
- data/lib/infinum_json_api_setup/rspec/matchers/have_resource_count_of.rb +37 -0
- data/lib/infinum_json_api_setup/rspec/matchers/include_all_resource_ids.rb +51 -0
- data/lib/infinum_json_api_setup/rspec/matchers/include_all_resource_ids_sorted.rb +21 -0
- data/lib/infinum_json_api_setup/rspec/matchers/include_all_resource_string_ids.rb +17 -0
- data/lib/infinum_json_api_setup/rspec/matchers/include_error_detail.rb +37 -0
- data/lib/infinum_json_api_setup/rspec/matchers/include_related_resource.rb +42 -0
- data/lib/infinum_json_api_setup/rspec/matchers/json_body_matcher.rb +42 -0
- data/lib/infinum_json_api_setup/rspec/matchers/schema_matchers.rb +29 -0
- data/lib/infinum_json_api_setup/rspec.rb +21 -0
- data/lib/infinum_json_api_setup/version.rb +3 -0
- data/lib/infinum_json_api_setup.rb +16 -0
- data/spec/controllers/api/v1/base_controller_spec.rb +9 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +1 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/api/v1/base_controller.rb +11 -0
- data/spec/dummy/app/controllers/api/v1/locations_controller.rb +64 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/models/location.rb +38 -0
- data/spec/dummy/app/models/location_label.rb +3 -0
- data/spec/dummy/app/policies/application_policy.rb +51 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/app/web/api/v1/locations/label_serializer.rb +13 -0
- data/spec/dummy/app/web/api/v1/locations/policy.rb +28 -0
- data/spec/dummy/app/web/api/v1/locations/query.rb +12 -0
- data/spec/dummy/app/web/api/v1/locations/serializer.rb +15 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config/application.rb +39 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +15 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +65 -0
- data/spec/dummy/config/environments/production.rb +114 -0
- data/spec/dummy/config/environments/test.rb +60 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +10 -0
- data/spec/dummy/config/initializers/cors.rb +16 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/locales/json_api.en.yml +21 -0
- data/spec/dummy/config/puma.rb +43 -0
- data/spec/dummy/config/routes.rb +7 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/db/migrate/20210709100750_create_locations.rb +10 -0
- data/spec/dummy/db/migrate/20210714104736_create_location_labels.rb +10 -0
- data/spec/dummy/db/schema.rb +34 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/storage/.keep +0 -0
- data/spec/factories/location.rb +11 -0
- data/spec/factories/location_label.rb +6 -0
- data/spec/infinum_json_api_setup/rspec/matchers/have_empty_data_spec.rb +43 -0
- data/spec/infinum_json_api_setup/rspec/matchers/have_error_pointer_spec.rb +43 -0
- data/spec/infinum_json_api_setup/rspec/matchers/have_resource_count_of_spec.rb +43 -0
- data/spec/infinum_json_api_setup/rspec/matchers/include_all_resource_ids_sorted_spec.rb +53 -0
- data/spec/infinum_json_api_setup/rspec/matchers/include_all_resource_ids_spec.rb +49 -0
- data/spec/infinum_json_api_setup/rspec/matchers/include_all_resource_string_ids_spec.rb +49 -0
- data/spec/infinum_json_api_setup/rspec/matchers/include_error_detail_spec.rb +43 -0
- data/spec/infinum_json_api_setup/rspec/matchers/include_related_resource_spec.rb +43 -0
- data/spec/infinum_json_api_setup/rspec/matchers/util/body_parser.rb +30 -0
- data/spec/rails_helper.rb +77 -0
- data/spec/requests/api/v1/content_negotiation_spec.rb +29 -0
- data/spec/requests/api/v1/error_handling_spec.rb +114 -0
- data/spec/requests/api/v1/responder_spec.rb +91 -0
- data/spec/requests/api/v1/serializer_options_spec.rb +46 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/support/factory_bot.rb +7 -0
- data/spec/support/infinum_json_api_setup.rb +1 -0
- data/spec/support/rspec_expectations.rb +5 -0
- data/spec/support/test_helpers/matchers/response.rb +17 -0
- data/spec/support/test_helpers/request.rb +11 -0
- data/spec/support/test_helpers/response.rb +8 -0
- metadata +351 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::HaveResourceCountOf do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context 'when data has correct number of items' do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_body(JSON.dump(data: [1, 2]))
|
8
|
+
|
9
|
+
expect(response).to have_resource_count_of(2)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when data has wrong number of items' do
|
14
|
+
it 'fails and describes failure reason' do
|
15
|
+
response = response_with_body(JSON.dump(data: [1, 2]))
|
16
|
+
|
17
|
+
expect do
|
18
|
+
expect(response).to have_resource_count_of(3)
|
19
|
+
end.to fail_with('Expected response data to have 3 items, but had 2')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when response isn't valid JSON" do
|
24
|
+
it 'fails and describes failure reason' do
|
25
|
+
response = response_with_body('')
|
26
|
+
|
27
|
+
expect do
|
28
|
+
expect(response).to have_resource_count_of(1)
|
29
|
+
end.to fail_with('Failed to parse response body')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when response doesn't contain data attribute" do
|
34
|
+
it 'fails and describes failure reason' do
|
35
|
+
response = response_with_body('{}')
|
36
|
+
|
37
|
+
expect do
|
38
|
+
expect(response).to have_resource_count_of(1)
|
39
|
+
end.to fail_with('Failed to extract data from response body')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::IncludeAllResourceIdsSorted do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context "when ID's match" do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_ids([1, 2])
|
8
|
+
|
9
|
+
expect(response).to include_all_resource_ids_sorted([1, 2])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when ID's don't match" do
|
14
|
+
it 'fails and describes failure reason' do
|
15
|
+
response = response_with_ids([1, 2, 3])
|
16
|
+
|
17
|
+
expect do
|
18
|
+
expect(response).to include_all_resource_ids_sorted([4])
|
19
|
+
end.to fail_with("Expected response ID's([1, 2, 3]) to match [4]")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when ID's aren't correctly ordered" do
|
24
|
+
it 'fails and describes failure reason' do
|
25
|
+
response = response_with_ids([2, 1])
|
26
|
+
|
27
|
+
expect do
|
28
|
+
expect(response).to include_all_resource_ids_sorted([1, 2])
|
29
|
+
end.to fail_with("Expected response ID's([2, 1]) to match [1, 2]")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when response isn't valid JSON" do
|
34
|
+
it 'fails and describes failure reason' do
|
35
|
+
response = response_with_body('')
|
36
|
+
|
37
|
+
expect do
|
38
|
+
expect(response).to include_all_resource_ids_sorted([1])
|
39
|
+
end.to fail_with('Failed to parse response body')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when response doesn't contain data attribute" do
|
44
|
+
it 'fails and describes failure reason' do
|
45
|
+
response = response_with_body('{}')
|
46
|
+
|
47
|
+
expect do
|
48
|
+
expect(response).to include_all_resource_ids_sorted([1])
|
49
|
+
end.to fail_with('Failed to extract data from response body')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::IncludeAllResourceIds do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context "when ID's match" do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_ids([1, 2])
|
8
|
+
|
9
|
+
expect(response).to include_all_resource_ids([1, 2])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "doesn't consider ordering" do
|
13
|
+
response = response_with_ids([1, 2, 3])
|
14
|
+
|
15
|
+
expect(response).to include_all_resource_ids([3, 2, 1])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when ID's don't match" do
|
20
|
+
it 'fails and describes failure reason' do
|
21
|
+
response = response_with_ids([1, 2, 3])
|
22
|
+
|
23
|
+
expect do
|
24
|
+
expect(response).to include_all_resource_ids([4])
|
25
|
+
end.to fail_with("Expected response ID's([1, 2, 3]) to match [4]")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when response isn't valid JSON" do
|
30
|
+
it 'fails and describes failure reason' do
|
31
|
+
response = response_with_body('')
|
32
|
+
|
33
|
+
expect do
|
34
|
+
expect(response).to include_all_resource_ids([1])
|
35
|
+
end.to fail_with('Failed to parse response body')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when response doesn't contain data attribute" do
|
40
|
+
it 'fails and describes failure reason' do
|
41
|
+
response = response_with_body('{}')
|
42
|
+
|
43
|
+
expect do
|
44
|
+
expect(response).to include_all_resource_ids([1])
|
45
|
+
end.to fail_with('Failed to extract data from response body')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::IncludeAllResourceStringIds do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context "when ID's match" do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_ids(['1', '2'])
|
8
|
+
|
9
|
+
expect(response).to include_all_resource_string_ids(['1', '2'])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "doesn't consider ordering" do
|
13
|
+
response = response_with_ids(['1', '2', '3'])
|
14
|
+
|
15
|
+
expect(response).to include_all_resource_string_ids(['3', '2', '1'])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when ID's don't match" do
|
20
|
+
it 'fails and describes failure reason' do
|
21
|
+
response = response_with_ids(['1', '2', '3'])
|
22
|
+
|
23
|
+
expect do
|
24
|
+
expect(response).to include_all_resource_string_ids(['4'])
|
25
|
+
end.to fail_with('Expected response ID\'s(["1", "2", "3"]) to match ["4"]')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when response isn't valid JSON" do
|
30
|
+
it 'fails and describes failure reason' do
|
31
|
+
response = response_with_body('')
|
32
|
+
|
33
|
+
expect do
|
34
|
+
expect(response).to include_all_resource_string_ids(['1'])
|
35
|
+
end.to fail_with('Failed to parse response body')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when response doesn't contain data attribute" do
|
40
|
+
it 'fails and describes failure reason' do
|
41
|
+
response = response_with_body('{}')
|
42
|
+
|
43
|
+
expect do
|
44
|
+
expect(response).to include_all_resource_string_ids(['1'])
|
45
|
+
end.to fail_with('Failed to extract data from response body')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::IncludeErrorDetail do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context 'when body includes error detail' do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_body(JSON.dump(errors: [{ detail: 'a failure' }]))
|
8
|
+
|
9
|
+
expect(response).to include_error_detail('a failure')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when body doesn't include specified error detail" do
|
14
|
+
it 'fails and describes failure reason' do
|
15
|
+
response = response_with_body(JSON.dump(errors: []))
|
16
|
+
|
17
|
+
expect do
|
18
|
+
expect(response).to include_error_detail('a failure')
|
19
|
+
end.to fail_with("Expected error details to include 'a failure', but didn't")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when response isn't valid JSON" do
|
24
|
+
it 'fails and describes failure reason' do
|
25
|
+
response = response_with_body('')
|
26
|
+
|
27
|
+
expect do
|
28
|
+
expect(response).to include_error_detail('a failure')
|
29
|
+
end.to fail_with('Failed to parse response body')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when response doesn't contain data attribute" do
|
34
|
+
it 'fails and describes failure reason' do
|
35
|
+
response = response_with_body('{}')
|
36
|
+
|
37
|
+
expect do
|
38
|
+
expect(response).to include_error_detail('a failure')
|
39
|
+
end.to fail_with('Failed to extract errors from response body')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe InfinumJsonApiSetup::RSpec::Matchers::IncludeRelatedResource do
|
2
|
+
include TestHelpers::Matchers::Response
|
3
|
+
|
4
|
+
describe 'usage' do
|
5
|
+
context 'when body includes related resource' do
|
6
|
+
it 'matches' do
|
7
|
+
response = response_with_body(JSON.dump(included: [{ type: 'user', id: '1' }]))
|
8
|
+
|
9
|
+
expect(response).to include_related_resource('user', 1)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when body doesn't include specified error detail" do
|
14
|
+
it 'fails and describes failure reason' do
|
15
|
+
response = response_with_body(JSON.dump(included: []))
|
16
|
+
|
17
|
+
expect do
|
18
|
+
expect(response).to include_related_resource('user', 1)
|
19
|
+
end.to fail_with("Expected included items to include (type = user, id = 1), but didn't")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when response isn't valid JSON" do
|
24
|
+
it 'fails and describes failure reason' do
|
25
|
+
response = response_with_body('')
|
26
|
+
|
27
|
+
expect do
|
28
|
+
expect(response).to include_related_resource('user', 1)
|
29
|
+
end.to fail_with('Failed to parse response body')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when response doesn't contain data attribute" do
|
34
|
+
it 'fails and describes failure reason' do
|
35
|
+
response = response_with_body('{}')
|
36
|
+
|
37
|
+
expect do
|
38
|
+
expect(response).to include_related_resource('user', 1)
|
39
|
+
end.to fail_with('Failed to extract included from response body')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module InfinumJsonApiSetup
|
4
|
+
module RSpec
|
5
|
+
module Matchers
|
6
|
+
module Util
|
7
|
+
class BodyParser
|
8
|
+
# @param [String] attribute
|
9
|
+
def initialize(attribute)
|
10
|
+
@attribute = attribute
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [ActionDispatch::TestResponse] response
|
14
|
+
# @return [Array]
|
15
|
+
def process(response)
|
16
|
+
[true, JSON.parse(response.body).fetch(attribute)]
|
17
|
+
rescue JSON::ParserError => _e
|
18
|
+
[false, 'Failed to parse response body']
|
19
|
+
rescue KeyError => _e
|
20
|
+
[false, "Failed to extract #{attribute} from response body"]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :attribute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
ENV['RAILS_ENV'] ||= 'test'
|
5
|
+
require File.expand_path('../spec/dummy/config/environment', __dir__)
|
6
|
+
require 'rspec/rails'
|
7
|
+
|
8
|
+
Dir.chdir File.expand_path('dummy', __dir__) do
|
9
|
+
system 'bundle exec rails generate infinum_json_api_setup:install -f -q'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Add additional requires below this line. Rails is not loaded until this point!
|
13
|
+
require 'jsonapi/query_builder'
|
14
|
+
require 'jsonapi/serializer'
|
15
|
+
require 'pundit'
|
16
|
+
|
17
|
+
# Requires supporting ruby files with custom matchers and macros, etc, in
|
18
|
+
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
19
|
+
# run as spec files by default. This means that files in spec/support that end
|
20
|
+
# in _spec.rb will both be required and run as specs, causing the specs to be
|
21
|
+
# run twice. It is recommended that you do not name files matching this glob to
|
22
|
+
# end with _spec.rb. You can configure this pattern with the --pattern
|
23
|
+
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
|
24
|
+
#
|
25
|
+
# The following line is provided for convenience purposes. It has the downside
|
26
|
+
# of increasing the boot-up time by auto-requiring all files in the support
|
27
|
+
# directory. Alternatively, in the individual `*_spec.rb` files, manually
|
28
|
+
# require only the support files necessary.
|
29
|
+
#
|
30
|
+
Dir[File.expand_path('support/**/*.rb', __dir__)].sort.each { |f| require f }
|
31
|
+
|
32
|
+
# Checks for pending migrations and applies them before tests are run.
|
33
|
+
# If you are not using ActiveRecord, you can remove these lines.
|
34
|
+
begin
|
35
|
+
ActiveRecord::Migration.maintain_test_schema!
|
36
|
+
rescue ActiveRecord::PendingMigrationError => e
|
37
|
+
puts e.to_s.strip
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
RSpec.configure do |config|
|
41
|
+
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
|
42
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
43
|
+
|
44
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
45
|
+
# examples within a transaction, remove the following line or assign false
|
46
|
+
# instead of true.
|
47
|
+
config.use_transactional_fixtures = true
|
48
|
+
|
49
|
+
# You can uncomment this line to turn off ActiveRecord support entirely.
|
50
|
+
# config.use_active_record = false
|
51
|
+
|
52
|
+
# RSpec Rails can automatically mix in different behaviours to your tests
|
53
|
+
# based on their file location, for example enabling you to call `get` and
|
54
|
+
# `post` in specs under `spec/controllers`.
|
55
|
+
#
|
56
|
+
# You can disable this behaviour by removing the line below, and instead
|
57
|
+
# explicitly tag your specs with their type, e.g.:
|
58
|
+
#
|
59
|
+
# RSpec.describe UsersController, type: :controller do
|
60
|
+
# # ...
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# The different available types are documented in the features, such as in
|
64
|
+
# https://relishapp.com/rspec/rspec-rails/docs
|
65
|
+
config.infer_spec_type_from_file_location!
|
66
|
+
|
67
|
+
# Filter lines from Rails gems in backtraces.
|
68
|
+
config.filter_rails_from_backtrace!
|
69
|
+
# arbitrary gems may also be filtered via:
|
70
|
+
# config.filter_gems_from_backtrace("gem name")
|
71
|
+
|
72
|
+
# Enables running rspec --only-failures
|
73
|
+
config.example_status_persistence_file_path = 'spec/examples.txt'
|
74
|
+
|
75
|
+
config.include TestHelpers::Request, type: :request
|
76
|
+
config.include TestHelpers::Response, type: :request
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
describe 'Content negotiation', type: :request do
|
2
|
+
it 'passes through requests demanding JSON:API compliant response' do
|
3
|
+
get '/api/v1/locations', headers: { accept: 'application/vnd.api+json' }
|
4
|
+
|
5
|
+
expect(response).to have_http_status(:ok)
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'responds with 406 NotAcceptable to requests demanding non JSON:API compliant reponse' do
|
9
|
+
get '/api/v1/locations', headers: { accept: 'application/json' }
|
10
|
+
|
11
|
+
expect(response).to have_http_status(:not_acceptable)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'responds with 415 UnsupportedMediaType to requests containing non JSON:API compliant body' do
|
15
|
+
post '/api/v1/locations', params: { user: { name: 'Harry' } },
|
16
|
+
headers: { accept: 'application/vnd.api+json',
|
17
|
+
'content-type': 'application/x-www-form-urlencoded' }
|
18
|
+
|
19
|
+
expect(response).to have_http_status(:unsupported_media_type)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'skips Content-Type check when request has empty body' do
|
23
|
+
get '/api/v1/locations', params: { user: { name: 'Harry' } },
|
24
|
+
headers: { accept: 'application/vnd.api+json',
|
25
|
+
'content-type': 'application/x-www-form-urlencoded' }
|
26
|
+
|
27
|
+
expect(response).to have_http_status(:ok)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
describe 'Error handling', type: :request do
|
2
|
+
context "when request doesn't contain required parameters" do
|
3
|
+
it 'responds with 400 BadRequest' do
|
4
|
+
post '/api/v1/locations', params: {}, headers: default_headers
|
5
|
+
|
6
|
+
expect(response).to have_http_status(:bad_request)
|
7
|
+
error = json_response['errors'].first
|
8
|
+
expect(error['title']).to eq('Bad Request')
|
9
|
+
expect(error['detail']).to match('param is missing or the value is empty: location')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when request contains semantical errors' do
|
14
|
+
it 'responds with 422 UnprocessableEntity and error details' do
|
15
|
+
params = { latitude: 999, longitude: 999 }
|
16
|
+
|
17
|
+
post '/api/v1/locations', params: { location: params }.to_json, headers: default_headers
|
18
|
+
|
19
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
20
|
+
expect(json_response['errors'].map { |details| details['source'] }).to contain_exactly(
|
21
|
+
{ 'parameter' => 'latitude', 'pointer' => 'data/attributes/latitude' },
|
22
|
+
{ 'parameter' => 'longitude', 'pointer' => 'data/attributes/longitude' }
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when ActiveRecord can't find requested record" do
|
28
|
+
it 'responds with 404 NotFound' do
|
29
|
+
get '/api/v1/locations/0', headers: default_headers
|
30
|
+
|
31
|
+
expect(response).to have_http_status(:not_found)
|
32
|
+
expect(json_response['errors'].first['title']).to eq('Not found')
|
33
|
+
expect(json_response['errors'].first['detail']).to eq('Resource not found')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when client is not authorized to perform requested action' do
|
38
|
+
it 'responds with 403 Forbidden' do
|
39
|
+
loc = create(:location, :fourth_quadrant)
|
40
|
+
get "/api/v1/locations/#{loc.id}", headers: default_headers
|
41
|
+
|
42
|
+
expect(response).to have_http_status(:forbidden)
|
43
|
+
expect(json_response['errors'].first['title']).to eq('Forbidden')
|
44
|
+
expect(json_response['errors'].first['detail'])
|
45
|
+
.to eq('You are not allowed to perform this action')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'when request contains unpermitted sort params' do
|
50
|
+
let(:bugsnag) { class_double('Bugsnag', notify: nil) }
|
51
|
+
|
52
|
+
before do
|
53
|
+
stub_const('Bugsnag', bugsnag)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'notifies Bugsnag about the incident' do
|
57
|
+
get '/api/v1/locations?sort=-title', headers: default_headers
|
58
|
+
|
59
|
+
expect(bugsnag).to have_received(:notify)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'responds with 400 BadRequest' do
|
63
|
+
get '/api/v1/locations?sort=-title', headers: default_headers
|
64
|
+
|
65
|
+
expect(response).to have_http_status(:bad_request)
|
66
|
+
error = json_response['errors'].first
|
67
|
+
expect(error['title']).to eq('Bad Request')
|
68
|
+
expect(error['detail']).to eq('title is not a permitted sort attribute')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when action processing causes PG::Error' do
|
73
|
+
let(:location_model) { class_double('Location') }
|
74
|
+
let(:bugsnag) { class_double('Bugsnag', notify: nil) }
|
75
|
+
|
76
|
+
before do
|
77
|
+
stub_const('Bugsnag', bugsnag)
|
78
|
+
stub_const('Location', location_model)
|
79
|
+
allow(location_model).to receive(:find) { raise PG::Error }
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'notifies Bugsnag about the incident' do
|
83
|
+
get '/api/v1/locations/0', headers: default_headers
|
84
|
+
|
85
|
+
expect(bugsnag).to have_received(:notify)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'responds with 500 InternalServerError' do
|
89
|
+
get '/api/v1/locations/0', headers: default_headers
|
90
|
+
|
91
|
+
expect(response).to have_http_status(:internal_server_error)
|
92
|
+
expect(json_response['errors'].first['title']).to eq('Internal Server Error')
|
93
|
+
expect(json_response['errors'].first['detail']).to eq('Something went wrong')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'when client requests invalid locale' do
|
98
|
+
it 'responds with 500 InternalServerError' do
|
99
|
+
get '/api/v1/locations?locale=--', headers: default_headers
|
100
|
+
|
101
|
+
expect(response).to have_http_status(:bad_request)
|
102
|
+
expect(json_response['errors'].first['title']).to eq('Bad Request')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when client request contains unparsable params' do
|
107
|
+
it 'responds with 400 BadRequest' do
|
108
|
+
post '/api/v1/locations', params: '{', headers: default_headers
|
109
|
+
|
110
|
+
expect(response).to have_http_status(:bad_request)
|
111
|
+
expect(json_response['errors'].first['title']).to eq('Bad Request')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
describe 'Responder', type: :request do
|
2
|
+
context 'when GET request' do
|
3
|
+
it 'responds with 200 OK' do
|
4
|
+
get '/api/v1/locations', headers: default_headers
|
5
|
+
|
6
|
+
expect(response).to have_http_status(:ok)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'when POST request' do
|
11
|
+
context 'without errors' do
|
12
|
+
it 'responds with 201 Created' do
|
13
|
+
params = { latitude: 1, longitude: 1 }
|
14
|
+
|
15
|
+
post '/api/v1/locations', params: { location: params }.to_json, headers: default_headers
|
16
|
+
|
17
|
+
expect(response).to have_http_status(:created)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with errors' do
|
22
|
+
it 'responds with 400 BadRequest' do
|
23
|
+
post '/api/v1/locations', params: {}, headers: default_headers
|
24
|
+
|
25
|
+
expect(response).to have_http_status(:bad_request)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when PUT request' do
|
31
|
+
context 'without errors' do
|
32
|
+
it 'responds with 200 OK' do
|
33
|
+
location = create(:location, latitude: 1, longitude: 1)
|
34
|
+
params = { latitude: 2 }
|
35
|
+
|
36
|
+
put "/api/v1/locations/#{location.id}", params: { location: params }.to_json,
|
37
|
+
headers: default_headers
|
38
|
+
|
39
|
+
expect(response).to have_http_status(:ok)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with errors' do
|
44
|
+
it 'responds with 422 UnprocessableEntity' do
|
45
|
+
location = create(:location, latitude: 1, longitude: 1)
|
46
|
+
params = { latitude: 999 }
|
47
|
+
|
48
|
+
put "/api/v1/locations/#{location.id}", params: { location: params }.to_json,
|
49
|
+
headers: default_headers
|
50
|
+
|
51
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'when PATCH request' do
|
57
|
+
context 'without errors' do
|
58
|
+
it 'responds with 200 OK' do
|
59
|
+
location = create(:location, latitude: 1, longitude: 1)
|
60
|
+
params = { latitude: 2 }
|
61
|
+
|
62
|
+
patch "/api/v1/locations/#{location.id}", params: { location: params }.to_json,
|
63
|
+
headers: default_headers
|
64
|
+
|
65
|
+
expect(response).to have_http_status(:ok)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with errors' do
|
70
|
+
it 'responds with 422 UnprocessableEntity' do
|
71
|
+
location = create(:location, latitude: 1, longitude: 1)
|
72
|
+
params = { latitude: 999 }
|
73
|
+
|
74
|
+
patch "/api/v1/locations/#{location.id}", params: { location: params }.to_json,
|
75
|
+
headers: default_headers
|
76
|
+
|
77
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'when DELETE request' do
|
83
|
+
it 'responds with 204 NoContent' do
|
84
|
+
location = create(:location, latitude: 1, longitude: 1)
|
85
|
+
|
86
|
+
delete "/api/v1/locations/#{location.id}", headers: default_headers
|
87
|
+
|
88
|
+
expect(response).to have_http_status(:no_content)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
describe 'Serializer options', type: :request do
|
5
|
+
it 'adds meta with pagination information' do
|
6
|
+
get '/api/v1/locations', params: {}, headers: default_headers
|
7
|
+
|
8
|
+
expect(response).to have_http_status(:ok)
|
9
|
+
expect(json_response).to have_key('meta')
|
10
|
+
expect(json_response['meta'].keys).to include(
|
11
|
+
'current_page', 'total_pages', 'total_count', 'padding', 'page_size', 'max_page_size'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'adds links' do
|
16
|
+
get '/api/v1/locations', params: {}, headers: default_headers
|
17
|
+
|
18
|
+
expect(response).to have_http_status(:ok)
|
19
|
+
expect(json_response).to have_key('links')
|
20
|
+
expect(json_response['links'].keys).to include('self', 'first', 'last')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'adds client requested fields information to links' do
|
24
|
+
q = { fields: { locations: 'latitude' } }
|
25
|
+
get "/api/v1/locations?#{q.to_query}", params: {}, headers: default_headers
|
26
|
+
|
27
|
+
expect(response).to have_http_status(:ok)
|
28
|
+
expect(query_params(json_response.dig('links', 'self')))
|
29
|
+
.to include('fields[locations]' => ['latitude'])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'adds client requested include information to links' do
|
33
|
+
q = { include: 'labels' }
|
34
|
+
get "/api/v1/locations?#{q.to_query}", params: {}, headers: default_headers
|
35
|
+
|
36
|
+
expect(response).to have_http_status(:ok)
|
37
|
+
expect(query_params(json_response.dig('links', 'self')))
|
38
|
+
.to include('include' => ['labels'])
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def query_params(link)
|
44
|
+
CGI.parse(URI(link).query)
|
45
|
+
end
|
46
|
+
end
|