api_matchers 0.6.1 → 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 -1
- data/Gemfile +1 -1
- data/History.markdown +62 -46
- 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 +154 -48
- data/.rvmrc.example +0 -1
- data/.travis.yml +0 -12
- 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,39 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::HTTPStatus::BeUnprocessable do
|
|
4
|
+
describe "actual.to be_unprocessable" do
|
|
5
|
+
it "passes for status 422" do
|
|
6
|
+
expect(422).to be_unprocessable
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it "fails for status 200" do
|
|
10
|
+
expect {
|
|
11
|
+
expect(200).to be_unprocessable
|
|
12
|
+
}.to fail_with("expected response to be unprocessable (422). Got: 200")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "fails for status 400" do
|
|
16
|
+
expect {
|
|
17
|
+
expect(400).to be_unprocessable
|
|
18
|
+
}.to fail_with("expected response to be unprocessable (422). Got: 400")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "actual.not_to be_unprocessable" do
|
|
23
|
+
it "passes for non-422 status" do
|
|
24
|
+
expect(200).not_to be_unprocessable
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "fails for 422 status" do
|
|
28
|
+
expect {
|
|
29
|
+
expect(422).not_to be_unprocessable
|
|
30
|
+
}.to fail_with("expected response NOT to be unprocessable (422). Got: 422")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "alias be_unprocessable_entity" do
|
|
35
|
+
it "works as alias for be_unprocessable" do
|
|
36
|
+
expect(422).to be_unprocessable_entity
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::HTTPStatus::HaveHttpStatus do
|
|
4
|
+
describe "actual.to have_http_status" do
|
|
5
|
+
context "with integer status code" do
|
|
6
|
+
it "passes when the status matches" do
|
|
7
|
+
expect(200).to have_http_status(200)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "fails when the status does not match" do
|
|
11
|
+
expect {
|
|
12
|
+
expect(404).to have_http_status(200)
|
|
13
|
+
}.to fail_with("expected response to have HTTP status 200. Got: 404")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "with symbol status code" do
|
|
18
|
+
it "passes when the status matches :ok" do
|
|
19
|
+
expect(200).to have_http_status(:ok)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "passes when the status matches :created" do
|
|
23
|
+
expect(201).to have_http_status(:created)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "passes when the status matches :not_found" do
|
|
27
|
+
expect(404).to have_http_status(:not_found)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "passes when the status matches :unprocessable_entity" do
|
|
31
|
+
expect(422).to have_http_status(:unprocessable_entity)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "fails when the status does not match" do
|
|
35
|
+
expect {
|
|
36
|
+
expect(500).to have_http_status(:ok)
|
|
37
|
+
}.to fail_with("expected response to have HTTP status 200 (ok). Got: 500")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "raises error for unknown symbol" do
|
|
41
|
+
expect {
|
|
42
|
+
expect(200).to have_http_status(:unknown_status)
|
|
43
|
+
}.to raise_error(ArgumentError, /Unknown status code symbol/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "actual.not_to have_http_status" do
|
|
49
|
+
it "passes when the status does not match" do
|
|
50
|
+
expect(404).not_to have_http_status(200)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "fails when the status matches" do
|
|
54
|
+
expect {
|
|
55
|
+
expect(200).not_to have_http_status(200)
|
|
56
|
+
}.to fail_with("expected response NOT to have HTTP status 200. Got: 200")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
describe "with configuration" do
|
|
61
|
+
before do
|
|
62
|
+
APIMatchers.setup { |config| config.http_status_method = :status }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
after do
|
|
66
|
+
APIMatchers.setup { |config| config.http_status_method = nil }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "extracts status from response object" do
|
|
70
|
+
response = OpenStruct.new(status: 200)
|
|
71
|
+
expect(response).to have_http_status(:ok)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "fails when response status does not match" do
|
|
75
|
+
response = OpenStruct.new(status: 404)
|
|
76
|
+
expect {
|
|
77
|
+
expect(response).to have_http_status(:ok)
|
|
78
|
+
}.to fail_with(/expected response to have HTTP status 200/)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonApi::BeJsonApiCompliant do
|
|
4
|
+
describe "actual.to be_json_api_compliant" do
|
|
5
|
+
context "with valid JSON:API structure" do
|
|
6
|
+
it "passes with data object" do
|
|
7
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John"}}}'
|
|
8
|
+
expect(json).to be_json_api_compliant
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes with data array" do
|
|
12
|
+
json = '{"data": [{"id": "1", "type": "users"}, {"id": "2", "type": "users"}]}'
|
|
13
|
+
expect(json).to be_json_api_compliant
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "passes with null data" do
|
|
17
|
+
json = '{"data": null}'
|
|
18
|
+
expect(json).to be_json_api_compliant
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "passes with errors array" do
|
|
22
|
+
json = '{"errors": [{"status": "404", "title": "Not Found"}]}'
|
|
23
|
+
expect(json).to be_json_api_compliant
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "passes with meta only" do
|
|
27
|
+
json = '{"meta": {"total": 100}}'
|
|
28
|
+
expect(json).to be_json_api_compliant
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "passes with relationships" do
|
|
32
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}}}}'
|
|
33
|
+
expect(json).to be_json_api_compliant
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context "with invalid JSON:API structure" do
|
|
38
|
+
it "fails when top level is not an object" do
|
|
39
|
+
json = '[1, 2, 3]'
|
|
40
|
+
expect {
|
|
41
|
+
expect(json).to be_json_api_compliant
|
|
42
|
+
}.to fail_with(/top-level must be an object/)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "fails when missing data, errors, and meta" do
|
|
46
|
+
json = '{"included": []}'
|
|
47
|
+
expect {
|
|
48
|
+
expect(json).to be_json_api_compliant
|
|
49
|
+
}.to fail_with(/must contain at least one of: data, errors, or meta/)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "fails when data and errors coexist" do
|
|
53
|
+
json = '{"data": {"id": "1", "type": "users"}, "errors": []}'
|
|
54
|
+
expect {
|
|
55
|
+
expect(json).to be_json_api_compliant
|
|
56
|
+
}.to fail_with(/data and errors must not coexist/)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "fails when resource is missing type" do
|
|
60
|
+
json = '{"data": {"id": "1"}}'
|
|
61
|
+
expect {
|
|
62
|
+
expect(json).to be_json_api_compliant
|
|
63
|
+
}.to fail_with(/must contain 'type'/)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "fails when type is not a string" do
|
|
67
|
+
json = '{"data": {"id": "1", "type": 123}}'
|
|
68
|
+
expect {
|
|
69
|
+
expect(json).to be_json_api_compliant
|
|
70
|
+
}.to fail_with(/type must be a string/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "fails when attributes is not an object" do
|
|
74
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": "invalid"}}'
|
|
75
|
+
expect {
|
|
76
|
+
expect(json).to be_json_api_compliant
|
|
77
|
+
}.to fail_with(/attributes must be an object/)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "fails when relationships is not an object" do
|
|
81
|
+
json = '{"data": {"id": "1", "type": "users", "relationships": "invalid"}}'
|
|
82
|
+
expect {
|
|
83
|
+
expect(json).to be_json_api_compliant
|
|
84
|
+
}.to fail_with(/relationships must be an object/)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "fails when errors is not an array" do
|
|
88
|
+
json = '{"errors": {"message": "error"}}'
|
|
89
|
+
expect {
|
|
90
|
+
expect(json).to be_json_api_compliant
|
|
91
|
+
}.to fail_with(/errors must be an array/)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe "actual.not_to be_json_api_compliant" do
|
|
97
|
+
it "passes when not compliant" do
|
|
98
|
+
json = '{"result": "success"}'
|
|
99
|
+
expect(json).not_to be_json_api_compliant
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "fails when compliant" do
|
|
103
|
+
json = '{"data": {"id": "1", "type": "users"}}'
|
|
104
|
+
expect {
|
|
105
|
+
expect(json).not_to be_json_api_compliant
|
|
106
|
+
}.to fail_with(/expected response NOT to be JSON:API compliant/)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonApi::HaveJsonApiAttributes do
|
|
4
|
+
describe "actual.to have_json_api_attributes" do
|
|
5
|
+
context "with single resource" do
|
|
6
|
+
it "passes when all attributes are present" do
|
|
7
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John", "email": "john@example.com"}}}'
|
|
8
|
+
expect(json).to have_json_api_attributes(:name, :email)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when checking subset of attributes" do
|
|
12
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John", "email": "john@example.com", "age": 30}}}'
|
|
13
|
+
expect(json).to have_json_api_attributes(:name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when attribute is missing" do
|
|
17
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John"}}}'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to have_json_api_attributes(:name, :email)
|
|
20
|
+
}.to fail_with(/Missing: \["email"\]/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with array of resources" do
|
|
25
|
+
it "checks attributes of first resource" do
|
|
26
|
+
json = '{"data": [{"id": "1", "type": "users", "attributes": {"name": "John"}}, {"id": "2", "type": "users", "attributes": {"name": "Jane"}}]}'
|
|
27
|
+
expect(json).to have_json_api_attributes(:name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "when no attributes found" do
|
|
32
|
+
it "fails when data has no attributes" do
|
|
33
|
+
json = '{"data": {"id": "1", "type": "users"}}'
|
|
34
|
+
expect {
|
|
35
|
+
expect(json).to have_json_api_attributes(:name)
|
|
36
|
+
}.to fail_with(/but no attributes were found/)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "fails when data is missing" do
|
|
40
|
+
json = '{"errors": []}'
|
|
41
|
+
expect {
|
|
42
|
+
expect(json).to have_json_api_attributes(:name)
|
|
43
|
+
}.to fail_with(/but no attributes were found/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "actual.not_to have_json_api_attributes" do
|
|
49
|
+
it "passes when not all attributes are present" do
|
|
50
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John"}}}'
|
|
51
|
+
expect(json).not_to have_json_api_attributes(:email)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "fails when all attributes are present" do
|
|
55
|
+
json = '{"data": {"id": "1", "type": "users", "attributes": {"name": "John", "email": "john@example.com"}}}'
|
|
56
|
+
expect {
|
|
57
|
+
expect(json).not_to have_json_api_attributes(:name, :email)
|
|
58
|
+
}.to fail_with(/expected JSON:API data NOT to have attributes/)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonApi::HaveJsonApiData do
|
|
4
|
+
describe "actual.to have_json_api_data" do
|
|
5
|
+
context "basic data presence" do
|
|
6
|
+
it "passes when data is present" do
|
|
7
|
+
json = '{"data": {"id": "1", "type": "users"}}'
|
|
8
|
+
expect(json).to have_json_api_data
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when data is null" do
|
|
12
|
+
json = '{"data": null}'
|
|
13
|
+
expect(json).to have_json_api_data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "passes when data is an array" do
|
|
17
|
+
json = '{"data": [{"id": "1", "type": "users"}]}'
|
|
18
|
+
expect(json).to have_json_api_data
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "fails when data is missing" do
|
|
22
|
+
json = '{"errors": []}'
|
|
23
|
+
expect {
|
|
24
|
+
expect(json).to have_json_api_data
|
|
25
|
+
}.to fail_with(/expected response to have JSON:API data/)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "with of_type" do
|
|
30
|
+
it "passes when type matches" do
|
|
31
|
+
json = '{"data": {"id": "1", "type": "users"}}'
|
|
32
|
+
expect(json).to have_json_api_data.of_type("users")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "passes when type matches for array" do
|
|
36
|
+
json = '{"data": [{"id": "1", "type": "users"}, {"id": "2", "type": "users"}]}'
|
|
37
|
+
expect(json).to have_json_api_data.of_type(:users)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it "fails when type does not match" do
|
|
41
|
+
json = '{"data": {"id": "1", "type": "posts"}}'
|
|
42
|
+
expect {
|
|
43
|
+
expect(json).to have_json_api_data.of_type("users")
|
|
44
|
+
}.to fail_with(/expected JSON:API data to have type 'users'. Got type: 'posts'/)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
context "with with_id" do
|
|
49
|
+
it "passes when id matches" do
|
|
50
|
+
json = '{"data": {"id": "123", "type": "users"}}'
|
|
51
|
+
expect(json).to have_json_api_data.with_id("123")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "passes when id is in array" do
|
|
55
|
+
json = '{"data": [{"id": "1", "type": "users"}, {"id": "2", "type": "users"}]}'
|
|
56
|
+
expect(json).to have_json_api_data.with_id("2")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "fails when id does not match" do
|
|
60
|
+
json = '{"data": {"id": "123", "type": "users"}}'
|
|
61
|
+
expect {
|
|
62
|
+
expect(json).to have_json_api_data.with_id("456")
|
|
63
|
+
}.to fail_with(/expected JSON:API data to have id '456'. Got id: '123'/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context "combining of_type and with_id" do
|
|
68
|
+
it "passes when both match" do
|
|
69
|
+
json = '{"data": {"id": "123", "type": "users"}}'
|
|
70
|
+
expect(json).to have_json_api_data.of_type("users").with_id("123")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "fails when type does not match" do
|
|
74
|
+
json = '{"data": {"id": "123", "type": "posts"}}'
|
|
75
|
+
expect {
|
|
76
|
+
expect(json).to have_json_api_data.of_type("users").with_id("123")
|
|
77
|
+
}.to fail_with(/expected JSON:API data to have type 'users'/)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
describe "actual.not_to have_json_api_data" do
|
|
83
|
+
it "passes when data is missing" do
|
|
84
|
+
json = '{"errors": []}'
|
|
85
|
+
expect(json).not_to have_json_api_data
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "fails when data is present" do
|
|
89
|
+
json = '{"data": {"id": "1", "type": "users"}}'
|
|
90
|
+
expect {
|
|
91
|
+
expect(json).not_to have_json_api_data
|
|
92
|
+
}.to fail_with(/expected response NOT to have JSON:API data/)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonApi::HaveJsonApiRelationships do
|
|
4
|
+
describe "actual.to have_json_api_relationships" do
|
|
5
|
+
context "with single resource" do
|
|
6
|
+
it "passes when all relationships are present" do
|
|
7
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}, "comments": {"data": []}}}}'
|
|
8
|
+
expect(json).to have_json_api_relationships(:author, :comments)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when checking subset of relationships" do
|
|
12
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}, "comments": {"data": []}}}}'
|
|
13
|
+
expect(json).to have_json_api_relationships(:author)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when relationship is missing" do
|
|
17
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}}}}'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to have_json_api_relationships(:author, :comments)
|
|
20
|
+
}.to fail_with(/Missing: \["comments"\]/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with array of resources" do
|
|
25
|
+
it "checks relationships of first resource" do
|
|
26
|
+
json = '{"data": [{"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}}}, {"id": "2", "type": "posts", "relationships": {"author": {"data": {"id": "2", "type": "users"}}}}]}'
|
|
27
|
+
expect(json).to have_json_api_relationships(:author)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "when no relationships found" do
|
|
32
|
+
it "fails when data has no relationships" do
|
|
33
|
+
json = '{"data": {"id": "1", "type": "posts"}}'
|
|
34
|
+
expect {
|
|
35
|
+
expect(json).to have_json_api_relationships(:author)
|
|
36
|
+
}.to fail_with(/but no relationships were found/)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "fails when data is missing" do
|
|
40
|
+
json = '{"errors": []}'
|
|
41
|
+
expect {
|
|
42
|
+
expect(json).to have_json_api_relationships(:author)
|
|
43
|
+
}.to fail_with(/but no relationships were found/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "actual.not_to have_json_api_relationships" do
|
|
49
|
+
it "passes when not all relationships are present" do
|
|
50
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}}}}'
|
|
51
|
+
expect(json).not_to have_json_api_relationships(:comments)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "fails when all relationships are present" do
|
|
55
|
+
json = '{"data": {"id": "1", "type": "posts", "relationships": {"author": {"data": {"id": "1", "type": "users"}}, "comments": {"data": []}}}}'
|
|
56
|
+
expect {
|
|
57
|
+
expect(json).not_to have_json_api_relationships(:author, :comments)
|
|
58
|
+
}.to fail_with(/expected JSON:API data NOT to have relationships/)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonStructure::HaveJsonKeys do
|
|
4
|
+
describe "actual.to have_json_keys" do
|
|
5
|
+
context "with JSON string" do
|
|
6
|
+
it "passes when all keys are present" do
|
|
7
|
+
json = '{"id": 1, "name": "John", "email": "john@example.com"}'
|
|
8
|
+
expect(json).to have_json_keys(:id, :name, :email)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when checking subset of keys" do
|
|
12
|
+
json = '{"id": 1, "name": "John", "email": "john@example.com"}'
|
|
13
|
+
expect(json).to have_json_keys(:id, :name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when a key is missing" do
|
|
17
|
+
json = '{"id": 1, "name": "John"}'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to have_json_keys(:id, :name, :email)
|
|
20
|
+
}.to fail_with(/Missing: \["email"\]/)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "fails when multiple keys are missing" do
|
|
24
|
+
json = '{"id": 1}'
|
|
25
|
+
expect {
|
|
26
|
+
expect(json).to have_json_keys(:id, :name, :email)
|
|
27
|
+
}.to fail_with(/Missing: \["name", "email"\]/)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "with Hash" do
|
|
32
|
+
it "passes when all keys are present" do
|
|
33
|
+
data = { id: 1, name: "John", email: "john@example.com" }
|
|
34
|
+
expect(data).to have_json_keys(:id, :name, :email)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "with at_path" do
|
|
39
|
+
it "checks keys at specified path" do
|
|
40
|
+
json = '{"user": {"id": 1, "name": "John"}}'
|
|
41
|
+
expect(json).to have_json_keys(:id, :name).at_path("user")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "fails when path does not exist" do
|
|
45
|
+
json = '{"data": {"id": 1}}'
|
|
46
|
+
expect {
|
|
47
|
+
expect(json).to have_json_keys(:id).at_path("user")
|
|
48
|
+
}.to fail_with(/Missing: \["id"\]/)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
describe "actual.not_to have_json_keys" do
|
|
54
|
+
it "passes when not all keys are present" do
|
|
55
|
+
json = '{"id": 1, "name": "John"}'
|
|
56
|
+
expect(json).not_to have_json_keys(:id, :name, :email)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "fails when all keys are present" do
|
|
60
|
+
json = '{"id": 1, "name": "John"}'
|
|
61
|
+
expect {
|
|
62
|
+
expect(json).not_to have_json_keys(:id, :name)
|
|
63
|
+
}.to fail_with(/expected JSON NOT to have keys/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe "with configuration" do
|
|
68
|
+
before do
|
|
69
|
+
APIMatchers.setup { |config| config.response_body_method = :body }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
after do
|
|
73
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "extracts body from response object" do
|
|
77
|
+
response = OpenStruct.new(body: '{"id": 1, "name": "John"}')
|
|
78
|
+
expect(response).to have_json_keys(:id, :name)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::JsonStructure::HaveJsonType do
|
|
4
|
+
describe "actual.to have_json_type" do
|
|
5
|
+
context "Integer type" do
|
|
6
|
+
it "passes for integer value" do
|
|
7
|
+
json = '{"id": 42}'
|
|
8
|
+
expect(json).to have_json_type(Integer).at_path("id")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "fails for non-integer value" do
|
|
12
|
+
json = '{"id": "42"}'
|
|
13
|
+
expect {
|
|
14
|
+
expect(json).to have_json_type(Integer).at_path("id")
|
|
15
|
+
}.to fail_with(/expected JSON value at 'id' to be of type Integer/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "String type" do
|
|
20
|
+
it "passes for string value" do
|
|
21
|
+
json = '{"name": "John"}'
|
|
22
|
+
expect(json).to have_json_type(String).at_path("name")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "fails for non-string value" do
|
|
26
|
+
json = '{"name": 123}'
|
|
27
|
+
expect {
|
|
28
|
+
expect(json).to have_json_type(String).at_path("name")
|
|
29
|
+
}.to fail_with(/expected JSON value at 'name' to be of type String/)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "Boolean type" do
|
|
34
|
+
it "passes for true value" do
|
|
35
|
+
json = '{"active": true}'
|
|
36
|
+
expect(json).to have_json_type(:boolean).at_path("active")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "passes for false value" do
|
|
40
|
+
json = '{"active": false}'
|
|
41
|
+
expect(json).to have_json_type(:boolean).at_path("active")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "fails for non-boolean value" do
|
|
45
|
+
json = '{"active": "true"}'
|
|
46
|
+
expect {
|
|
47
|
+
expect(json).to have_json_type(:boolean).at_path("active")
|
|
48
|
+
}.to fail_with(/expected JSON value at 'active' to be of type Boolean/)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "Array type" do
|
|
53
|
+
it "passes for array value" do
|
|
54
|
+
json = '{"items": [1, 2, 3]}'
|
|
55
|
+
expect(json).to have_json_type(Array).at_path("items")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "fails for non-array value" do
|
|
59
|
+
json = '{"items": "not an array"}'
|
|
60
|
+
expect {
|
|
61
|
+
expect(json).to have_json_type(Array).at_path("items")
|
|
62
|
+
}.to fail_with(/expected JSON value at 'items' to be of type Array/)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "Hash type" do
|
|
67
|
+
it "passes for hash/object value" do
|
|
68
|
+
json = '{"user": {"name": "John"}}'
|
|
69
|
+
expect(json).to have_json_type(Hash).at_path("user")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it "fails for non-hash value" do
|
|
73
|
+
json = '{"user": "not a hash"}'
|
|
74
|
+
expect {
|
|
75
|
+
expect(json).to have_json_type(Hash).at_path("user")
|
|
76
|
+
}.to fail_with(/expected JSON value at 'user' to be of type Hash/)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
context "NilClass type" do
|
|
81
|
+
it "passes for null value" do
|
|
82
|
+
json = '{"value": null}'
|
|
83
|
+
expect(json).to have_json_type(NilClass).at_path("value")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "fails for non-null value" do
|
|
87
|
+
json = '{"value": "something"}'
|
|
88
|
+
expect {
|
|
89
|
+
expect(json).to have_json_type(NilClass).at_path("value")
|
|
90
|
+
}.to fail_with(/expected JSON value at 'value' to be of type NilClass/)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
context "Numeric type" do
|
|
95
|
+
it "passes for integer value" do
|
|
96
|
+
json = '{"count": 42}'
|
|
97
|
+
expect(json).to have_json_type(Numeric).at_path("count")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "passes for float value" do
|
|
101
|
+
json = '{"price": 19.99}'
|
|
102
|
+
expect(json).to have_json_type(Numeric).at_path("price")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
context "nested path" do
|
|
107
|
+
it "navigates nested structure" do
|
|
108
|
+
json = '{"user": {"profile": {"age": 30}}}'
|
|
109
|
+
expect(json).to have_json_type(Integer).at_path("user.profile.age")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
context "at root" do
|
|
114
|
+
it "checks type at root level without at_path" do
|
|
115
|
+
json = '[1, 2, 3]'
|
|
116
|
+
expect(json).to have_json_type(Array)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "actual.not_to have_json_type" do
|
|
122
|
+
it "passes when type does not match" do
|
|
123
|
+
json = '{"id": "42"}'
|
|
124
|
+
expect(json).not_to have_json_type(Integer).at_path("id")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
it "fails when type matches" do
|
|
128
|
+
json = '{"id": 42}'
|
|
129
|
+
expect {
|
|
130
|
+
expect(json).not_to have_json_type(Integer).at_path("id")
|
|
131
|
+
}.to fail_with(/expected JSON value at 'id' NOT to be of type Integer/)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|