api_matchers 0.6.2 → 1.0.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 +5 -5
- data/.github/workflows/ci.yml +30 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/History.markdown +62 -50
- data/README.markdown +1070 -114
- data/TODO.markdown +39 -3
- data/api_matchers.gemspec +13 -6
- data/lib/api_matchers/collection/base.rb +65 -0
- data/lib/api_matchers/collection/be_sorted_by.rb +97 -0
- data/lib/api_matchers/collection/have_json_size.rb +54 -0
- data/lib/api_matchers/core/exceptions.rb +5 -0
- data/lib/api_matchers/core/find_in_json.rb +57 -28
- data/lib/api_matchers/core/http_status_codes.rb +118 -0
- data/lib/api_matchers/core/json_path_finder.rb +87 -0
- data/lib/api_matchers/core/parser.rb +4 -2
- data/lib/api_matchers/core/rspec_matchers.rb +130 -37
- data/lib/api_matchers/core/setup.rb +49 -23
- data/lib/api_matchers/core/value_normalizer.rb +26 -0
- data/lib/api_matchers/error_response/base.rb +77 -0
- data/lib/api_matchers/error_response/have_error.rb +69 -0
- data/lib/api_matchers/error_response/have_error_on.rb +173 -0
- data/lib/api_matchers/hateoas/base.rb +77 -0
- data/lib/api_matchers/hateoas/have_link.rb +102 -0
- data/lib/api_matchers/headers/base.rb +7 -7
- data/lib/api_matchers/headers/be_json.rb +2 -4
- data/lib/api_matchers/headers/be_xml.rb +2 -4
- data/lib/api_matchers/headers/have_cache_control.rb +90 -0
- data/lib/api_matchers/headers/have_cors_headers.rb +102 -0
- data/lib/api_matchers/headers/have_header.rb +102 -0
- data/lib/api_matchers/http_status/base.rb +48 -0
- data/lib/api_matchers/http_status/be_client_error.rb +17 -0
- data/lib/api_matchers/http_status/be_forbidden.rb +17 -0
- data/lib/api_matchers/http_status/be_no_content.rb +17 -0
- data/lib/api_matchers/http_status/be_not_found.rb +17 -0
- data/lib/api_matchers/http_status/be_redirect.rb +17 -0
- data/lib/api_matchers/http_status/be_server_error.rb +17 -0
- data/lib/api_matchers/http_status/be_successful.rb +17 -0
- data/lib/api_matchers/http_status/be_unauthorized.rb +17 -0
- data/lib/api_matchers/http_status/be_unprocessable.rb +17 -0
- data/lib/api_matchers/http_status/have_http_status.rb +39 -0
- data/lib/api_matchers/json_api/base.rb +83 -0
- data/lib/api_matchers/json_api/be_json_api_compliant.rb +158 -0
- data/lib/api_matchers/json_api/have_json_api_attributes.rb +62 -0
- data/lib/api_matchers/json_api/have_json_api_data.rb +110 -0
- data/lib/api_matchers/json_api/have_json_api_relationships.rb +62 -0
- data/lib/api_matchers/json_structure/base.rb +65 -0
- data/lib/api_matchers/json_structure/have_json_keys.rb +55 -0
- data/lib/api_matchers/json_structure/have_json_type.rb +72 -0
- data/lib/api_matchers/pagination/base.rb +73 -0
- data/lib/api_matchers/pagination/be_paginated.rb +73 -0
- data/lib/api_matchers/pagination/have_pagination_links.rb +74 -0
- data/lib/api_matchers/pagination/have_total_count.rb +77 -0
- data/lib/api_matchers/response_body/base.rb +10 -9
- data/lib/api_matchers/response_body/have_json.rb +2 -4
- data/lib/api_matchers/response_body/have_json_node.rb +45 -1
- data/lib/api_matchers/response_body/have_node.rb +2 -0
- data/lib/api_matchers/response_body/have_xml_node.rb +13 -14
- data/lib/api_matchers/response_body/match_json_schema.rb +89 -0
- data/lib/api_matchers/version.rb +3 -1
- data/lib/api_matchers.rb +75 -14
- data/spec/api_matchers/collection/be_sorted_by_spec.rb +110 -0
- data/spec/api_matchers/collection/have_json_size_spec.rb +101 -0
- data/spec/api_matchers/error_response/have_error_on_spec.rb +123 -0
- data/spec/api_matchers/error_response/have_error_spec.rb +108 -0
- data/spec/api_matchers/hateoas/have_link_spec.rb +105 -0
- data/spec/api_matchers/headers/base_spec.rb +8 -3
- data/spec/api_matchers/headers/be_json_spec.rb +1 -1
- data/spec/api_matchers/headers/be_xml_spec.rb +1 -1
- data/spec/api_matchers/headers/have_cache_control_spec.rb +102 -0
- data/spec/api_matchers/headers/have_cors_headers_spec.rb +74 -0
- data/spec/api_matchers/headers/have_header_spec.rb +88 -0
- data/spec/api_matchers/http_status/be_client_error_spec.rb +53 -0
- data/spec/api_matchers/http_status/be_forbidden_spec.rb +33 -0
- data/spec/api_matchers/http_status/be_no_content_spec.rb +33 -0
- data/spec/api_matchers/http_status/be_not_found_spec.rb +39 -0
- data/spec/api_matchers/http_status/be_redirect_spec.rb +55 -0
- data/spec/api_matchers/http_status/be_server_error_spec.rb +49 -0
- data/spec/api_matchers/http_status/be_successful_spec.rb +78 -0
- data/spec/api_matchers/http_status/be_unauthorized_spec.rb +33 -0
- data/spec/api_matchers/http_status/be_unprocessable_spec.rb +39 -0
- data/spec/api_matchers/http_status/have_http_status_spec.rb +81 -0
- data/spec/api_matchers/json_api/be_json_api_compliant_spec.rb +109 -0
- data/spec/api_matchers/json_api/have_json_api_attributes_spec.rb +61 -0
- data/spec/api_matchers/json_api/have_json_api_data_spec.rb +95 -0
- data/spec/api_matchers/json_api/have_json_api_relationships_spec.rb +61 -0
- data/spec/api_matchers/json_structure/have_json_keys_spec.rb +81 -0
- data/spec/api_matchers/json_structure/have_json_type_spec.rb +134 -0
- data/spec/api_matchers/pagination/be_paginated_spec.rb +95 -0
- data/spec/api_matchers/pagination/have_pagination_links_spec.rb +80 -0
- data/spec/api_matchers/pagination/have_total_count_spec.rb +85 -0
- data/spec/api_matchers/response_body/base_spec.rb +15 -7
- data/spec/api_matchers/response_body/have_json_node_spec.rb +57 -0
- data/spec/api_matchers/response_body/match_json_schema_spec.rb +86 -0
- metadata +152 -47
- data/.rvmrc.example +0 -1
- data/.travis.yml +0 -12
- data/Gemfile.lock +0 -46
- data/lib/api_matchers/http_status_code/base.rb +0 -32
- data/lib/api_matchers/http_status_code/be_bad_request.rb +0 -25
- data/lib/api_matchers/http_status_code/be_forbidden.rb +0 -21
- data/lib/api_matchers/http_status_code/be_internal_server_error.rb +0 -25
- data/lib/api_matchers/http_status_code/be_not_found.rb +0 -25
- data/lib/api_matchers/http_status_code/be_ok.rb +0 -25
- data/lib/api_matchers/http_status_code/be_unauthorized.rb +0 -25
- data/lib/api_matchers/http_status_code/be_unprocessable_entity.rb +0 -25
- data/lib/api_matchers/http_status_code/create_resource.rb +0 -25
- data/spec/api_matchers/http_status_code/base_spec.rb +0 -12
- data/spec/api_matchers/http_status_code/be_bad_request_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_forbidden_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_internal_server_error_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_not_found_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_ok_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_unauthorized_spec.rb +0 -49
- data/spec/api_matchers/http_status_code/be_unprocessable_entity_spec.rb +0 -27
- data/spec/api_matchers/http_status_code/create_resource_spec.rb +0 -49
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Pagination::BePaginated do
|
|
4
|
+
describe "actual.to be_paginated" do
|
|
5
|
+
context "with pagination in meta" do
|
|
6
|
+
it "passes when meta has page" do
|
|
7
|
+
json = '{"data": [], "meta": {"page": 1, "per_page": 10}}'
|
|
8
|
+
expect(json).to be_paginated
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when meta has total_count" do
|
|
12
|
+
json = '{"data": [], "meta": {"total_count": 100}}'
|
|
13
|
+
expect(json).to be_paginated
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "passes when meta has offset/limit" do
|
|
17
|
+
json = '{"data": [], "meta": {"offset": 0, "limit": 10}}'
|
|
18
|
+
expect(json).to be_paginated
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "with pagination links" do
|
|
23
|
+
it "passes when links has next" do
|
|
24
|
+
json = '{"data": [], "links": {"next": "/api/users?page=2"}}'
|
|
25
|
+
expect(json).to be_paginated
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "passes when links has prev" do
|
|
29
|
+
json = '{"data": [], "links": {"prev": "/api/users?page=1"}}'
|
|
30
|
+
expect(json).to be_paginated
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "passes when links has first/last" do
|
|
34
|
+
json = '{"data": [], "links": {"first": "/api/users?page=1", "last": "/api/users?page=10"}}'
|
|
35
|
+
expect(json).to be_paginated
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
context "with pagination at root level" do
|
|
40
|
+
it "passes when root has page" do
|
|
41
|
+
json = '{"items": [], "page": 1, "per_page": 10}'
|
|
42
|
+
expect(json).to be_paginated
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "passes when root has total" do
|
|
46
|
+
json = '{"items": [], "total": 100}'
|
|
47
|
+
expect(json).to be_paginated
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when not paginated" do
|
|
52
|
+
it "fails when no pagination keys found" do
|
|
53
|
+
json = '{"data": [{"id": 1}]}'
|
|
54
|
+
expect {
|
|
55
|
+
expect(json).to be_paginated
|
|
56
|
+
}.to fail_with(/expected response to be paginated/)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe "actual.not_to be_paginated" do
|
|
62
|
+
it "passes when not paginated" do
|
|
63
|
+
json = '{"data": [{"id": 1}]}'
|
|
64
|
+
expect(json).not_to be_paginated
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "fails when paginated" do
|
|
68
|
+
json = '{"data": [], "meta": {"page": 1}}'
|
|
69
|
+
expect {
|
|
70
|
+
expect(json).not_to be_paginated
|
|
71
|
+
}.to fail_with(/expected response NOT to be paginated/)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe "with configuration" do
|
|
76
|
+
before do
|
|
77
|
+
APIMatchers.setup do |config|
|
|
78
|
+
config.response_body_method = :body
|
|
79
|
+
config.pagination_meta_path = 'pagination'
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
after do
|
|
84
|
+
APIMatchers.setup do |config|
|
|
85
|
+
config.response_body_method = nil
|
|
86
|
+
config.pagination_meta_path = nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "uses configured pagination_meta_path" do
|
|
91
|
+
response = OpenStruct.new(body: '{"data": [], "pagination": {"page": 1}}')
|
|
92
|
+
expect(response).to be_paginated
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Pagination::HavePaginationLinks do
|
|
4
|
+
describe "actual.to have_pagination_links" do
|
|
5
|
+
context "with standard links" do
|
|
6
|
+
it "passes when specified links are present" do
|
|
7
|
+
json = '{"data": [], "links": {"next": "/page/2", "prev": "/page/1"}}'
|
|
8
|
+
expect(json).to have_pagination_links(:next, :prev)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when checking single link" do
|
|
12
|
+
json = '{"data": [], "links": {"next": "/page/2"}}'
|
|
13
|
+
expect(json).to have_pagination_links(:next)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when links are missing" do
|
|
17
|
+
json = '{"data": [], "links": {"next": "/page/2"}}'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to have_pagination_links(:next, :prev)
|
|
20
|
+
}.to fail_with(/Missing: \["prev"\]/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with various link names" do
|
|
25
|
+
it "passes with first/last links" do
|
|
26
|
+
json = '{"data": [], "links": {"first": "/page/1", "last": "/page/10"}}'
|
|
27
|
+
expect(json).to have_pagination_links(:first, :last)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "treats previous as prev" do
|
|
31
|
+
json = '{"data": [], "links": {"previous": "/page/1"}}'
|
|
32
|
+
expect(json).to have_pagination_links(:prev)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "when links path does not exist" do
|
|
37
|
+
it "fails with descriptive message" do
|
|
38
|
+
json = '{"data": []}'
|
|
39
|
+
expect {
|
|
40
|
+
expect(json).to have_pagination_links(:next)
|
|
41
|
+
}.to fail_with(/but no links were found/)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
describe "actual.not_to have_pagination_links" do
|
|
47
|
+
it "passes when links are not present" do
|
|
48
|
+
json = '{"data": [], "links": {"self": "/current"}}'
|
|
49
|
+
expect(json).not_to have_pagination_links(:next, :prev)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "fails when all specified links are present" do
|
|
53
|
+
json = '{"data": [], "links": {"next": "/page/2", "prev": "/page/1"}}'
|
|
54
|
+
expect {
|
|
55
|
+
expect(json).not_to have_pagination_links(:next, :prev)
|
|
56
|
+
}.to fail_with(/expected response NOT to have pagination links/)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "with configuration" do
|
|
61
|
+
before do
|
|
62
|
+
APIMatchers.setup do |config|
|
|
63
|
+
config.response_body_method = :body
|
|
64
|
+
config.pagination_links_path = '_links'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
after do
|
|
69
|
+
APIMatchers.setup do |config|
|
|
70
|
+
config.response_body_method = nil
|
|
71
|
+
config.pagination_links_path = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "uses configured pagination_links_path" do
|
|
76
|
+
response = OpenStruct.new(body: '{"data": [], "_links": {"next": "/page/2"}}')
|
|
77
|
+
expect(response).to have_pagination_links(:next)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Pagination::HaveTotalCount do
|
|
4
|
+
describe "actual.to have_total_count" do
|
|
5
|
+
context "with total in meta" do
|
|
6
|
+
it "passes when total matches" do
|
|
7
|
+
json = '{"data": [], "meta": {"total": 100}}'
|
|
8
|
+
expect(json).to have_total_count(100)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes with total_count key" do
|
|
12
|
+
json = '{"data": [], "meta": {"total_count": 50}}'
|
|
13
|
+
expect(json).to have_total_count(50)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "passes with totalCount key (camelCase)" do
|
|
17
|
+
json = '{"data": [], "meta": {"totalCount": 75}}'
|
|
18
|
+
expect(json).to have_total_count(75)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "fails when count does not match" do
|
|
22
|
+
json = '{"data": [], "meta": {"total": 100}}'
|
|
23
|
+
expect {
|
|
24
|
+
expect(json).to have_total_count(50)
|
|
25
|
+
}.to fail_with(/expected total count to be 50. Got: 100/)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "with total at root level" do
|
|
30
|
+
it "passes when total is at root" do
|
|
31
|
+
json = '{"items": [], "total": 100}'
|
|
32
|
+
expect(json).to have_total_count(100)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "passes with count key at root" do
|
|
36
|
+
json = '{"items": [], "count": 25}'
|
|
37
|
+
expect(json).to have_total_count(25)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context "when total count is not found" do
|
|
42
|
+
it "fails with descriptive message" do
|
|
43
|
+
json = '{"data": []}'
|
|
44
|
+
expect {
|
|
45
|
+
expect(json).to have_total_count(100)
|
|
46
|
+
}.to fail_with(/but no total count field was found/)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
describe "actual.not_to have_total_count" do
|
|
52
|
+
it "passes when count does not match" do
|
|
53
|
+
json = '{"data": [], "meta": {"total": 100}}'
|
|
54
|
+
expect(json).not_to have_total_count(50)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "fails when count matches" do
|
|
58
|
+
json = '{"data": [], "meta": {"total": 100}}'
|
|
59
|
+
expect {
|
|
60
|
+
expect(json).not_to have_total_count(100)
|
|
61
|
+
}.to fail_with(/expected total count NOT to be 100, but it was/)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
describe "with configuration" do
|
|
66
|
+
before do
|
|
67
|
+
APIMatchers.setup do |config|
|
|
68
|
+
config.response_body_method = :body
|
|
69
|
+
config.pagination_meta_path = 'pagination'
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
after do
|
|
74
|
+
APIMatchers.setup do |config|
|
|
75
|
+
config.response_body_method = nil
|
|
76
|
+
config.pagination_meta_path = nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "uses configured pagination_meta_path" do
|
|
81
|
+
response = OpenStruct.new(body: '{"data": [], "pagination": {"total": 100}}')
|
|
82
|
+
expect(response).to have_total_count(100)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
RSpec.describe APIMatchers::ResponseBody::Base do
|
|
4
|
-
|
|
5
|
-
subject { APIMatchers::ResponseBody::Base.new(setup: setup, expected_node: :status) }
|
|
4
|
+
subject { APIMatchers::ResponseBody::Base.new(expected_node: :status) }
|
|
6
5
|
|
|
7
6
|
describe "#matches?" do
|
|
8
7
|
it "should raise Not Implemented Error" do
|
|
@@ -13,8 +12,8 @@ RSpec.describe APIMatchers::ResponseBody::Base do
|
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
describe "#setup" do
|
|
16
|
-
it "
|
|
17
|
-
expect(subject.setup).to
|
|
15
|
+
it "returns the global Setup class" do
|
|
16
|
+
expect(subject.setup).to eq APIMatchers::Core::Setup
|
|
18
17
|
end
|
|
19
18
|
end
|
|
20
19
|
|
|
@@ -28,6 +27,14 @@ RSpec.describe APIMatchers::ResponseBody::Base do
|
|
|
28
27
|
let(:body) { { :foo => :bar}.to_json }
|
|
29
28
|
|
|
30
29
|
context 'when have configuration' do
|
|
30
|
+
before do
|
|
31
|
+
APIMatchers.setup { |config| config.response_body_method = :body }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
after do
|
|
35
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
it "should call the method when is config" do
|
|
32
39
|
subject.actual = OpenStruct.new(:body => body)
|
|
33
40
|
expect(subject.response_body).to eql body
|
|
@@ -35,8 +42,9 @@ RSpec.describe APIMatchers::ResponseBody::Base do
|
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
context 'when dont have configuration' do
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
before do
|
|
46
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
47
|
+
end
|
|
40
48
|
|
|
41
49
|
it "should return the actual when do not have config" do
|
|
42
50
|
subject.actual = body
|
|
@@ -44,4 +52,4 @@ RSpec.describe APIMatchers::ResponseBody::Base do
|
|
|
44
52
|
end
|
|
45
53
|
end
|
|
46
54
|
end
|
|
47
|
-
end
|
|
55
|
+
end
|
|
@@ -275,4 +275,61 @@ RSpec.describe APIMatchers::ResponseBody::HaveJsonNode do
|
|
|
275
275
|
}.to fail_with(%Q{expected to have node called: 'bar'. Got: '{"baz":"foo"}'})
|
|
276
276
|
end
|
|
277
277
|
end
|
|
278
|
+
|
|
279
|
+
describe "including" do
|
|
280
|
+
it "passes when array includes an element matching the hash" do
|
|
281
|
+
json = '{"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}'
|
|
282
|
+
expect(json).to have_json_node(:users).including(name: "Alice")
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it "passes when array includes an element matching multiple attributes" do
|
|
286
|
+
json = '{"users": [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]}'
|
|
287
|
+
expect(json).to have_json_node(:users).including(name: "Alice", age: 30)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
it "fails when array does not include matching element" do
|
|
291
|
+
json = '{"users": [{"name": "Alice"}, {"name": "Bob"}]}'
|
|
292
|
+
expect {
|
|
293
|
+
expect(json).to have_json_node(:users).including(name: "Charlie")
|
|
294
|
+
}.to fail_with(/expected to have node called: 'users'/)
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
it "fails when node is not an array" do
|
|
298
|
+
json = '{"user": {"name": "Alice"}}'
|
|
299
|
+
expect {
|
|
300
|
+
expect(json).to have_json_node(:user).including(name: "Alice")
|
|
301
|
+
}.to fail_with(/expected to have node called: 'user'/)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it "passes when array includes a simple value" do
|
|
305
|
+
json = '{"tags": ["ruby", "rails", "api"]}'
|
|
306
|
+
expect(json).to have_json_node(:tags).including("ruby")
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
describe "including_all" do
|
|
311
|
+
it "passes when array includes all specified elements" do
|
|
312
|
+
json = '{"items": [{"id": 1}, {"id": 2}, {"id": 3}]}'
|
|
313
|
+
expect(json).to have_json_node(:items).including_all([{id: 1}, {id: 2}])
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "fails when array is missing some elements" do
|
|
317
|
+
json = '{"items": [{"id": 1}, {"id": 3}]}'
|
|
318
|
+
expect {
|
|
319
|
+
expect(json).to have_json_node(:items).including_all([{id: 1}, {id: 2}])
|
|
320
|
+
}.to fail_with(/expected to have node called: 'items'/)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it "passes when array includes all simple values" do
|
|
324
|
+
json = '{"numbers": [1, 2, 3, 4, 5]}'
|
|
325
|
+
expect(json).to have_json_node(:numbers).including_all([1, 3, 5])
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
it "fails when node is not an array" do
|
|
329
|
+
json = '{"item": {"id": 1}}'
|
|
330
|
+
expect {
|
|
331
|
+
expect(json).to have_json_node(:item).including_all([{id: 1}])
|
|
332
|
+
}.to fail_with(/expected to have node called: 'item'/)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
278
335
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::ResponseBody::MatchJsonSchema do
|
|
4
|
+
let(:schema) do
|
|
5
|
+
{
|
|
6
|
+
type: "object",
|
|
7
|
+
required: ["id", "name"],
|
|
8
|
+
properties: {
|
|
9
|
+
id: { type: "integer" },
|
|
10
|
+
name: { type: "string" },
|
|
11
|
+
email: { type: "string" }
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe "actual.to match_json_schema" do
|
|
17
|
+
it "passes when JSON matches the schema" do
|
|
18
|
+
json = '{"id": 1, "name": "John", "email": "john@example.com"}'
|
|
19
|
+
expect(json).to match_json_schema(schema)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "passes when JSON has extra properties not in schema" do
|
|
23
|
+
json = '{"id": 1, "name": "John", "extra": "field"}'
|
|
24
|
+
expect(json).to match_json_schema(schema)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "fails when required property is missing" do
|
|
28
|
+
json = '{"id": 1}'
|
|
29
|
+
expect {
|
|
30
|
+
expect(json).to match_json_schema(schema)
|
|
31
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /Expected JSON to match schema/)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "fails when property has wrong type" do
|
|
35
|
+
json = '{"id": "not_an_integer", "name": "John"}'
|
|
36
|
+
expect {
|
|
37
|
+
expect(json).to match_json_schema(schema)
|
|
38
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /Expected JSON to match schema/)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "actual.not_to match_json_schema" do
|
|
43
|
+
it "passes when JSON does not match schema" do
|
|
44
|
+
json = '{"id": 1}'
|
|
45
|
+
expect(json).not_to match_json_schema(schema)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "fails when JSON matches schema" do
|
|
49
|
+
json = '{"id": 1, "name": "John"}'
|
|
50
|
+
expect {
|
|
51
|
+
expect(json).not_to match_json_schema(schema)
|
|
52
|
+
}.to raise_error(RSpec::Expectations::ExpectationNotMetError, /Expected JSON to NOT match schema/)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "with string schema" do
|
|
57
|
+
it "accepts schema as JSON string" do
|
|
58
|
+
json = '{"id": 1, "name": "John"}'
|
|
59
|
+
schema_string = '{"type": "object", "required": ["id"], "properties": {"id": {"type": "integer"}}}'
|
|
60
|
+
expect(json).to match_json_schema(schema_string)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "with invalid JSON" do
|
|
65
|
+
it "raises InvalidJSON error" do
|
|
66
|
+
expect {
|
|
67
|
+
expect("not valid json").to match_json_schema(schema)
|
|
68
|
+
}.to raise_error(APIMatchers::InvalidJSON)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "with response_body_method configured" do
|
|
73
|
+
before do
|
|
74
|
+
APIMatchers.setup { |config| config.response_body_method = :body }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
after do
|
|
78
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it "uses configured method to get response body" do
|
|
82
|
+
response = OpenStruct.new(body: '{"id": 1, "name": "John"}')
|
|
83
|
+
expect(response).to match_json_schema(schema)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|