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
data/lib/api_matchers.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "api_matchers/version"
|
|
2
4
|
require "active_support/core_ext/object"
|
|
3
5
|
require "active_support/core_ext/class"
|
|
@@ -5,26 +7,15 @@ require "active_support/core_ext/class"
|
|
|
5
7
|
module APIMatchers
|
|
6
8
|
autoload :RSpecMatchers, 'api_matchers/core/rspec_matchers'
|
|
7
9
|
|
|
8
|
-
# HTTP Status Code Matchers
|
|
9
|
-
#
|
|
10
|
-
module HTTPStatusCode
|
|
11
|
-
autoload :Base, 'api_matchers/http_status_code/base'
|
|
12
|
-
autoload :BeBadRequest, 'api_matchers/http_status_code/be_bad_request'
|
|
13
|
-
autoload :BeNotFound, 'api_matchers/http_status_code/be_not_found'
|
|
14
|
-
autoload :BeInternalServerError, 'api_matchers/http_status_code/be_internal_server_error'
|
|
15
|
-
autoload :BeUnauthorized, 'api_matchers/http_status_code/be_unauthorized'
|
|
16
|
-
autoload :BeOk, 'api_matchers/http_status_code/be_ok'
|
|
17
|
-
autoload :BeUnprocessableEntity, 'api_matchers/http_status_code/be_unprocessable_entity'
|
|
18
|
-
autoload :BeForbidden, 'api_matchers/http_status_code/be_forbidden'
|
|
19
|
-
autoload :CreateResource, 'api_matchers/http_status_code/create_resource'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
10
|
# Content Type Matchers
|
|
23
11
|
#
|
|
24
12
|
module Headers
|
|
25
13
|
autoload :Base, 'api_matchers/headers/base'
|
|
26
14
|
autoload :BeXML, 'api_matchers/headers/be_xml'
|
|
27
15
|
autoload :BeJSON, 'api_matchers/headers/be_json'
|
|
16
|
+
autoload :HaveHeader, 'api_matchers/headers/have_header'
|
|
17
|
+
autoload :HaveCorsHeaders, 'api_matchers/headers/have_cors_headers'
|
|
18
|
+
autoload :HaveCacheControl, 'api_matchers/headers/have_cache_control'
|
|
28
19
|
end
|
|
29
20
|
|
|
30
21
|
# Response Body Matchers
|
|
@@ -35,15 +26,85 @@ module APIMatchers
|
|
|
35
26
|
autoload :HaveJson, 'api_matchers/response_body/have_json'
|
|
36
27
|
autoload :HaveXmlNode, 'api_matchers/response_body/have_xml_node'
|
|
37
28
|
autoload :HaveNode, 'api_matchers/response_body/have_node'
|
|
29
|
+
autoload :MatchJsonSchema, 'api_matchers/response_body/match_json_schema'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# HTTP Status Matchers
|
|
33
|
+
#
|
|
34
|
+
module HTTPStatus
|
|
35
|
+
autoload :Base, 'api_matchers/http_status/base'
|
|
36
|
+
autoload :HaveHttpStatus, 'api_matchers/http_status/have_http_status'
|
|
37
|
+
autoload :BeSuccessful, 'api_matchers/http_status/be_successful'
|
|
38
|
+
autoload :BeRedirect, 'api_matchers/http_status/be_redirect'
|
|
39
|
+
autoload :BeClientError, 'api_matchers/http_status/be_client_error'
|
|
40
|
+
autoload :BeServerError, 'api_matchers/http_status/be_server_error'
|
|
41
|
+
autoload :BeNotFound, 'api_matchers/http_status/be_not_found'
|
|
42
|
+
autoload :BeUnauthorized, 'api_matchers/http_status/be_unauthorized'
|
|
43
|
+
autoload :BeForbidden, 'api_matchers/http_status/be_forbidden'
|
|
44
|
+
autoload :BeUnprocessable, 'api_matchers/http_status/be_unprocessable'
|
|
45
|
+
autoload :BeNoContent, 'api_matchers/http_status/be_no_content'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# JSON Structure Matchers
|
|
49
|
+
#
|
|
50
|
+
module JsonStructure
|
|
51
|
+
autoload :Base, 'api_matchers/json_structure/base'
|
|
52
|
+
autoload :HaveJsonKeys, 'api_matchers/json_structure/have_json_keys'
|
|
53
|
+
autoload :HaveJsonType, 'api_matchers/json_structure/have_json_type'
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Collection Matchers
|
|
57
|
+
#
|
|
58
|
+
module Collection
|
|
59
|
+
autoload :Base, 'api_matchers/collection/base'
|
|
60
|
+
autoload :HaveJsonSize, 'api_matchers/collection/have_json_size'
|
|
61
|
+
autoload :BeSortedBy, 'api_matchers/collection/be_sorted_by'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Pagination Matchers
|
|
65
|
+
#
|
|
66
|
+
module Pagination
|
|
67
|
+
autoload :Base, 'api_matchers/pagination/base'
|
|
68
|
+
autoload :BePaginated, 'api_matchers/pagination/be_paginated'
|
|
69
|
+
autoload :HavePaginationLinks, 'api_matchers/pagination/have_pagination_links'
|
|
70
|
+
autoload :HaveTotalCount, 'api_matchers/pagination/have_total_count'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Error Response Matchers
|
|
74
|
+
#
|
|
75
|
+
module ErrorResponse
|
|
76
|
+
autoload :Base, 'api_matchers/error_response/base'
|
|
77
|
+
autoload :HaveError, 'api_matchers/error_response/have_error'
|
|
78
|
+
autoload :HaveErrorOn, 'api_matchers/error_response/have_error_on'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# JSON:API Matchers
|
|
82
|
+
#
|
|
83
|
+
module JsonApi
|
|
84
|
+
autoload :Base, 'api_matchers/json_api/base'
|
|
85
|
+
autoload :BeJsonApiCompliant, 'api_matchers/json_api/be_json_api_compliant'
|
|
86
|
+
autoload :HaveJsonApiData, 'api_matchers/json_api/have_json_api_data'
|
|
87
|
+
autoload :HaveJsonApiAttributes, 'api_matchers/json_api/have_json_api_attributes'
|
|
88
|
+
autoload :HaveJsonApiRelationships, 'api_matchers/json_api/have_json_api_relationships'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# HATEOAS Matchers
|
|
92
|
+
#
|
|
93
|
+
module Hateoas
|
|
94
|
+
autoload :Base, 'api_matchers/hateoas/base'
|
|
95
|
+
autoload :HaveLink, 'api_matchers/hateoas/have_link'
|
|
38
96
|
end
|
|
39
97
|
|
|
40
98
|
# Core
|
|
41
99
|
#
|
|
42
100
|
module Core
|
|
43
101
|
autoload :FindInJSON, 'api_matchers/core/find_in_json'
|
|
102
|
+
autoload :ValueNormalizer, 'api_matchers/core/value_normalizer'
|
|
44
103
|
autoload :Parser, 'api_matchers/core/parser'
|
|
45
104
|
autoload :Setup, 'api_matchers/core/setup'
|
|
46
105
|
autoload :Exceptions, 'api_matchers/core/exceptions'
|
|
106
|
+
autoload :HTTPStatusCodes, 'api_matchers/core/http_status_codes'
|
|
107
|
+
autoload :JsonPathFinder, 'api_matchers/core/json_path_finder'
|
|
47
108
|
end
|
|
48
109
|
include ::APIMatchers::Core::Exceptions
|
|
49
110
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Collection::BeSortedBy do
|
|
4
|
+
describe "actual.to be_sorted_by" do
|
|
5
|
+
context "ascending order (default)" do
|
|
6
|
+
it "passes when array is sorted ascending" do
|
|
7
|
+
json = '[{"id": 1}, {"id": 2}, {"id": 3}]'
|
|
8
|
+
expect(json).to be_sorted_by(:id)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when array is sorted ascending (explicit)" do
|
|
12
|
+
json = '[{"id": 1}, {"id": 2}, {"id": 3}]'
|
|
13
|
+
expect(json).to be_sorted_by(:id).ascending
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when array is not sorted ascending" do
|
|
17
|
+
json = '[{"id": 3}, {"id": 1}, {"id": 2}]'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to be_sorted_by(:id).ascending
|
|
20
|
+
}.to fail_with(/expected JSON array at 'root' to be sorted by 'id' ascending/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "descending order" do
|
|
25
|
+
it "passes when array is sorted descending" do
|
|
26
|
+
json = '[{"id": 3}, {"id": 2}, {"id": 1}]'
|
|
27
|
+
expect(json).to be_sorted_by(:id).descending
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "fails when array is not sorted descending" do
|
|
31
|
+
json = '[{"id": 1}, {"id": 2}, {"id": 3}]'
|
|
32
|
+
expect {
|
|
33
|
+
expect(json).to be_sorted_by(:id).descending
|
|
34
|
+
}.to fail_with(/expected JSON array at 'root' to be sorted by 'id' descending/)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
context "with at_path" do
|
|
39
|
+
it "checks sorting at specified path" do
|
|
40
|
+
json = '{"users": [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]}'
|
|
41
|
+
expect(json).to be_sorted_by(:name).at_path("users")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "with string values" do
|
|
46
|
+
it "sorts strings alphabetically" do
|
|
47
|
+
json = '[{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]'
|
|
48
|
+
expect(json).to be_sorted_by(:name)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "with date strings" do
|
|
53
|
+
it "sorts date strings" do
|
|
54
|
+
json = '[{"created_at": "2023-01-01"}, {"created_at": "2023-06-15"}, {"created_at": "2023-12-31"}]'
|
|
55
|
+
expect(json).to be_sorted_by(:created_at)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "checks descending date order" do
|
|
59
|
+
json = '[{"created_at": "2023-12-31"}, {"created_at": "2023-06-15"}, {"created_at": "2023-01-01"}]'
|
|
60
|
+
expect(json).to be_sorted_by(:created_at).descending
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
context "with empty array" do
|
|
65
|
+
it "passes for empty array" do
|
|
66
|
+
json = '[]'
|
|
67
|
+
expect(json).to be_sorted_by(:id)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
context "with single element" do
|
|
72
|
+
it "passes for single element array" do
|
|
73
|
+
json = '[{"id": 1}]'
|
|
74
|
+
expect(json).to be_sorted_by(:id)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "when path does not exist" do
|
|
79
|
+
it "fails with descriptive message" do
|
|
80
|
+
json = '{"data": []}'
|
|
81
|
+
expect {
|
|
82
|
+
expect(json).to be_sorted_by(:id).at_path("users")
|
|
83
|
+
}.to fail_with(/but path was not found/)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
context "when value is not an array" do
|
|
88
|
+
it "fails with descriptive message" do
|
|
89
|
+
json = '{"user": {"id": 1}}'
|
|
90
|
+
expect {
|
|
91
|
+
expect(json).to be_sorted_by(:id).at_path("user")
|
|
92
|
+
}.to fail_with(/path was not found or value was not an array/)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "actual.not_to be_sorted_by" do
|
|
98
|
+
it "passes when not sorted" do
|
|
99
|
+
json = '[{"id": 3}, {"id": 1}, {"id": 2}]'
|
|
100
|
+
expect(json).not_to be_sorted_by(:id)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "fails when sorted" do
|
|
104
|
+
json = '[{"id": 1}, {"id": 2}, {"id": 3}]'
|
|
105
|
+
expect {
|
|
106
|
+
expect(json).not_to be_sorted_by(:id)
|
|
107
|
+
}.to fail_with(/expected JSON array at 'root' NOT to be sorted by 'id' ascending/)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Collection::HaveJsonSize do
|
|
4
|
+
describe "actual.to have_json_size" do
|
|
5
|
+
context "with array at root" do
|
|
6
|
+
it "passes when size matches" do
|
|
7
|
+
json = '[1, 2, 3]'
|
|
8
|
+
expect(json).to have_json_size(3)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "fails when size does not match" do
|
|
12
|
+
json = '[1, 2, 3]'
|
|
13
|
+
expect {
|
|
14
|
+
expect(json).to have_json_size(5)
|
|
15
|
+
}.to fail_with(/expected JSON collection at 'root' to have size 5. Got size: 3/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "with array at path" do
|
|
20
|
+
it "passes when size matches" do
|
|
21
|
+
json = '{"users": [{"id": 1}, {"id": 2}, {"id": 3}]}'
|
|
22
|
+
expect(json).to have_json_size(3).at_path("users")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "fails when size does not match" do
|
|
26
|
+
json = '{"users": [{"id": 1}, {"id": 2}]}'
|
|
27
|
+
expect {
|
|
28
|
+
expect(json).to have_json_size(5).at_path("users")
|
|
29
|
+
}.to fail_with(/expected JSON collection at 'users' to have size 5. Got size: 2/)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "with empty array" do
|
|
34
|
+
it "passes for size 0" do
|
|
35
|
+
json = '{"items": []}'
|
|
36
|
+
expect(json).to have_json_size(0).at_path("items")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "fails when expecting non-zero size" do
|
|
40
|
+
json = '{"items": []}'
|
|
41
|
+
expect {
|
|
42
|
+
expect(json).to have_json_size(5).at_path("items")
|
|
43
|
+
}.to fail_with(/Got size: 0/)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context "with hash (keys count)" do
|
|
48
|
+
it "checks size of hash keys" do
|
|
49
|
+
json = '{"a": 1, "b": 2, "c": 3}'
|
|
50
|
+
expect(json).to have_json_size(3)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "when path does not exist" do
|
|
55
|
+
it "fails with descriptive message" do
|
|
56
|
+
json = '{"data": []}'
|
|
57
|
+
expect {
|
|
58
|
+
expect(json).to have_json_size(5).at_path("users")
|
|
59
|
+
}.to fail_with(/but path was not found/)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "when value is not a collection" do
|
|
64
|
+
it "fails with descriptive message" do
|
|
65
|
+
json = '{"count": "not a collection"}'
|
|
66
|
+
expect {
|
|
67
|
+
expect(json).to have_json_size(5).at_path("count")
|
|
68
|
+
}.to fail_with(/path was not found or value was not a collection/)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "actual.not_to have_json_size" do
|
|
74
|
+
it "passes when size does not match" do
|
|
75
|
+
json = '[1, 2, 3]'
|
|
76
|
+
expect(json).not_to have_json_size(5)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "fails when size matches" do
|
|
80
|
+
json = '[1, 2, 3]'
|
|
81
|
+
expect {
|
|
82
|
+
expect(json).not_to have_json_size(3)
|
|
83
|
+
}.to fail_with(/expected JSON collection at 'root' NOT to have size 3/)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "with configuration" do
|
|
88
|
+
before do
|
|
89
|
+
APIMatchers.setup { |config| config.response_body_method = :body }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
after do
|
|
93
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "extracts body from response object" do
|
|
97
|
+
response = OpenStruct.new(body: '{"items": [1, 2, 3]}')
|
|
98
|
+
expect(response).to have_json_size(3).at_path("items")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::ErrorResponse::HaveErrorOn do
|
|
4
|
+
describe "actual.to have_error_on" do
|
|
5
|
+
context "with array of error objects (standard API style)" do
|
|
6
|
+
it "passes when error exists on field" do
|
|
7
|
+
json = '{"errors": [{"field": "email", "message": "is invalid"}]}'
|
|
8
|
+
expect(json).to have_error_on(:email)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "fails when no error on field" do
|
|
12
|
+
json = '{"errors": [{"field": "name", "message": "is required"}]}'
|
|
13
|
+
expect {
|
|
14
|
+
expect(json).to have_error_on(:email)
|
|
15
|
+
}.to fail_with(/Found errors on: \["name"\]/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "with Rails-style errors hash" do
|
|
20
|
+
it "passes when error exists on field" do
|
|
21
|
+
json = '{"email": ["is invalid", "is already taken"]}'
|
|
22
|
+
expect(json).to have_error_on(:email)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "fails when no error on field" do
|
|
26
|
+
json = '{"name": ["is required"]}'
|
|
27
|
+
expect {
|
|
28
|
+
expect(json).to have_error_on(:email)
|
|
29
|
+
}.to fail_with(/Found errors on: \["name"\]/)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context "with with_message" do
|
|
34
|
+
it "passes when message matches" do
|
|
35
|
+
json = '{"errors": [{"field": "email", "message": "is invalid"}]}'
|
|
36
|
+
expect(json).to have_error_on(:email).with_message("is invalid")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "fails when message does not match" do
|
|
40
|
+
json = '{"errors": [{"field": "email", "message": "is invalid"}]}'
|
|
41
|
+
expect {
|
|
42
|
+
expect(json).to have_error_on(:email).with_message("can't be blank")
|
|
43
|
+
}.to fail_with(/expected error on 'email' to have message/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "works with Rails-style errors" do
|
|
47
|
+
json = '{"email": ["is invalid", "is already taken"]}'
|
|
48
|
+
expect(json).to have_error_on(:email).with_message("is invalid")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "with matching (regex)" do
|
|
53
|
+
it "passes when message matches pattern" do
|
|
54
|
+
json = '{"errors": [{"field": "email", "message": "Email format is invalid"}]}'
|
|
55
|
+
expect(json).to have_error_on(:email).matching(/invalid/i)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "fails when message does not match pattern" do
|
|
59
|
+
json = '{"errors": [{"field": "email", "message": "is required"}]}'
|
|
60
|
+
expect {
|
|
61
|
+
expect(json).to have_error_on(:email).matching(/invalid/)
|
|
62
|
+
}.to fail_with(/expected error on 'email' to match/)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "when no errors found" do
|
|
67
|
+
it "fails with descriptive message" do
|
|
68
|
+
json = '{"data": {"id": 1}}'
|
|
69
|
+
expect {
|
|
70
|
+
expect(json).to have_error_on(:email)
|
|
71
|
+
}.to fail_with(/but no errors were found/)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context "with attribute key instead of field" do
|
|
76
|
+
it "works with attribute key" do
|
|
77
|
+
json = '{"errors": [{"attribute": "email", "message": "is invalid"}]}'
|
|
78
|
+
expect(json).to have_error_on(:email)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
describe "actual.not_to have_error_on" do
|
|
84
|
+
it "passes when no error on field" do
|
|
85
|
+
json = '{"errors": [{"field": "name", "message": "is required"}]}'
|
|
86
|
+
expect(json).not_to have_error_on(:email)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "fails when error exists on field" do
|
|
90
|
+
json = '{"errors": [{"field": "email", "message": "is invalid"}]}'
|
|
91
|
+
expect {
|
|
92
|
+
expect(json).not_to have_error_on(:email)
|
|
93
|
+
}.to fail_with(/expected NOT to have error on 'email'/)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "with configuration" do
|
|
98
|
+
before do
|
|
99
|
+
APIMatchers.setup do |config|
|
|
100
|
+
config.response_body_method = :body
|
|
101
|
+
config.errors_path = 'response.errors'
|
|
102
|
+
config.error_field_key = 'attribute'
|
|
103
|
+
config.error_message_key = 'detail'
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
after do
|
|
108
|
+
APIMatchers.setup do |config|
|
|
109
|
+
config.response_body_method = nil
|
|
110
|
+
config.errors_path = nil
|
|
111
|
+
config.error_field_key = nil
|
|
112
|
+
config.error_message_key = nil
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "uses configured paths and keys" do
|
|
117
|
+
response = OpenStruct.new(
|
|
118
|
+
body: '{"response": {"errors": [{"attribute": "email", "detail": "is invalid"}]}}'
|
|
119
|
+
)
|
|
120
|
+
expect(response).to have_error_on(:email).with_message("is invalid")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::ErrorResponse::HaveError do
|
|
4
|
+
describe "actual.to have_error" do
|
|
5
|
+
context "with errors array" do
|
|
6
|
+
it "passes when errors array is present" do
|
|
7
|
+
json = '{"errors": [{"message": "Name is required"}]}'
|
|
8
|
+
expect(json).to have_error
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "passes when errors array has multiple errors" do
|
|
12
|
+
json = '{"errors": [{"message": "Name is required"}, {"message": "Email is invalid"}]}'
|
|
13
|
+
expect(json).to have_errors
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "fails when errors array is empty" do
|
|
17
|
+
json = '{"errors": []}'
|
|
18
|
+
expect {
|
|
19
|
+
expect(json).to have_error
|
|
20
|
+
}.to fail_with(/expected response to have error/)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
context "with error object" do
|
|
25
|
+
it "passes when error key is present" do
|
|
26
|
+
json = '{"error": "Something went wrong"}'
|
|
27
|
+
expect(json).to have_error
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it "passes when error is an object" do
|
|
31
|
+
json = '{"error": {"code": "INVALID", "message": "Invalid input"}}'
|
|
32
|
+
expect(json).to have_error
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
context "with message key" do
|
|
37
|
+
it "passes when message is present" do
|
|
38
|
+
json = '{"message": "Resource not found"}'
|
|
39
|
+
expect(json).to have_error
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context "with errors at configured path" do
|
|
44
|
+
before do
|
|
45
|
+
APIMatchers.setup { |config| config.errors_path = 'response.errors' }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
after do
|
|
49
|
+
APIMatchers.setup { |config| config.errors_path = nil }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "finds errors at configured path" do
|
|
53
|
+
json = '{"response": {"errors": [{"message": "Error"}]}}'
|
|
54
|
+
expect(json).to have_error
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context "when no errors found" do
|
|
59
|
+
it "fails with descriptive message" do
|
|
60
|
+
json = '{"data": {"id": 1}}'
|
|
61
|
+
expect {
|
|
62
|
+
expect(json).to have_error
|
|
63
|
+
}.to fail_with(/expected response to have error/)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "actual.not_to have_error" do
|
|
69
|
+
it "passes when no errors present" do
|
|
70
|
+
json = '{"data": {"id": 1}}'
|
|
71
|
+
expect(json).not_to have_error
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "passes when errors array is empty" do
|
|
75
|
+
json = '{"errors": []}'
|
|
76
|
+
expect(json).not_to have_errors
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "fails when errors are present" do
|
|
80
|
+
json = '{"errors": [{"message": "Error"}]}'
|
|
81
|
+
expect {
|
|
82
|
+
expect(json).not_to have_error
|
|
83
|
+
}.to fail_with(/expected response NOT to have error/)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
describe "alias have_errors" do
|
|
88
|
+
it "works as alias for have_error" do
|
|
89
|
+
json = '{"errors": [{"message": "Error"}]}'
|
|
90
|
+
expect(json).to have_errors
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe "with configuration" do
|
|
95
|
+
before do
|
|
96
|
+
APIMatchers.setup { |config| config.response_body_method = :body }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
after do
|
|
100
|
+
APIMatchers.setup { |config| config.response_body_method = nil }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "extracts body from response object" do
|
|
104
|
+
response = OpenStruct.new(body: '{"errors": [{"message": "Error"}]}')
|
|
105
|
+
expect(response).to have_error
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe APIMatchers::Hateoas::HaveLink do
|
|
4
|
+
describe "actual.to have_link" do
|
|
5
|
+
context "with HAL-style links" do
|
|
6
|
+
it "passes when link is present" do
|
|
7
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
8
|
+
expect(json).to have_link(:self)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "fails when link is not present" do
|
|
12
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
13
|
+
expect {
|
|
14
|
+
expect(json).to have_link(:next)
|
|
15
|
+
}.to fail_with(/Available links: \["self"\]/)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
context "with simple links object" do
|
|
20
|
+
it "passes when link is present" do
|
|
21
|
+
json = '{"links": {"self": "/users/1"}}'
|
|
22
|
+
expect(json).to have_link(:self)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "with with_href (exact match)" do
|
|
27
|
+
it "passes when href matches exactly" do
|
|
28
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
29
|
+
expect(json).to have_link(:self).with_href("/users/1")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "fails when href does not match" do
|
|
33
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
34
|
+
expect {
|
|
35
|
+
expect(json).to have_link(:self).with_href("/users/2")
|
|
36
|
+
}.to fail_with(/expected link 'self' to have href '\/users\/2'. Got: '\/users\/1'/)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with with_href (regex)" do
|
|
41
|
+
it "passes when href matches pattern" do
|
|
42
|
+
json = '{"_links": {"self": {"href": "/users/123"}}}'
|
|
43
|
+
expect(json).to have_link(:self).with_href(/\/users\/\d+/)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "fails when href does not match pattern" do
|
|
47
|
+
json = '{"_links": {"self": {"href": "/posts/123"}}}'
|
|
48
|
+
expect {
|
|
49
|
+
expect(json).to have_link(:self).with_href(/\/users\/\d+/)
|
|
50
|
+
}.to fail_with(/expected link 'self' href to match/)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "with simple string href" do
|
|
55
|
+
it "extracts href from string value" do
|
|
56
|
+
json = '{"links": {"self": "/users/1"}}'
|
|
57
|
+
expect(json).to have_link(:self).with_href("/users/1")
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
context "when links not found" do
|
|
62
|
+
it "fails with descriptive message" do
|
|
63
|
+
json = '{"data": {"id": 1}}'
|
|
64
|
+
expect {
|
|
65
|
+
expect(json).to have_link(:self)
|
|
66
|
+
}.to fail_with(/but no links were found/)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "actual.not_to have_link" do
|
|
72
|
+
it "passes when link is not present" do
|
|
73
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
74
|
+
expect(json).not_to have_link(:next)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "fails when link is present" do
|
|
78
|
+
json = '{"_links": {"self": {"href": "/users/1"}}}'
|
|
79
|
+
expect {
|
|
80
|
+
expect(json).not_to have_link(:self)
|
|
81
|
+
}.to fail_with(/expected response NOT to have link 'self', but it was present/)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
describe "with configuration" do
|
|
86
|
+
before do
|
|
87
|
+
APIMatchers.setup do |config|
|
|
88
|
+
config.response_body_method = :body
|
|
89
|
+
config.links_path = 'links'
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
after do
|
|
94
|
+
APIMatchers.setup do |config|
|
|
95
|
+
config.response_body_method = nil
|
|
96
|
+
config.links_path = nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "uses configured links_path" do
|
|
101
|
+
response = OpenStruct.new(body: '{"links": {"self": {"href": "/users/1"}}}')
|
|
102
|
+
expect(response).to have_link(:self)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
RSpec.describe APIMatchers::Headers::Base do
|
|
4
|
-
|
|
5
|
-
subject { APIMatchers::Headers::Base.new(setup) }
|
|
4
|
+
subject { APIMatchers::Headers::Base.new }
|
|
6
5
|
|
|
7
6
|
describe "#matches?" do
|
|
8
7
|
it "should raise Not Implement Exception" do
|
|
9
8
|
expect { subject.matches?('application/xml') }.to raise_error(NotImplementedError, "not implemented on #{subject}")
|
|
10
9
|
end
|
|
11
10
|
end
|
|
12
|
-
|
|
11
|
+
|
|
12
|
+
describe "#setup" do
|
|
13
|
+
it "returns the global Setup class" do
|
|
14
|
+
expect(subject.setup).to eq APIMatchers::Core::Setup
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|