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.
Files changed (116) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +30 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +1 -1
  5. data/History.markdown +62 -50
  6. data/README.markdown +1070 -114
  7. data/TODO.markdown +39 -3
  8. data/api_matchers.gemspec +13 -6
  9. data/lib/api_matchers/collection/base.rb +65 -0
  10. data/lib/api_matchers/collection/be_sorted_by.rb +97 -0
  11. data/lib/api_matchers/collection/have_json_size.rb +54 -0
  12. data/lib/api_matchers/core/exceptions.rb +5 -0
  13. data/lib/api_matchers/core/find_in_json.rb +57 -28
  14. data/lib/api_matchers/core/http_status_codes.rb +118 -0
  15. data/lib/api_matchers/core/json_path_finder.rb +87 -0
  16. data/lib/api_matchers/core/parser.rb +4 -2
  17. data/lib/api_matchers/core/rspec_matchers.rb +130 -37
  18. data/lib/api_matchers/core/setup.rb +49 -23
  19. data/lib/api_matchers/core/value_normalizer.rb +26 -0
  20. data/lib/api_matchers/error_response/base.rb +77 -0
  21. data/lib/api_matchers/error_response/have_error.rb +69 -0
  22. data/lib/api_matchers/error_response/have_error_on.rb +173 -0
  23. data/lib/api_matchers/hateoas/base.rb +77 -0
  24. data/lib/api_matchers/hateoas/have_link.rb +102 -0
  25. data/lib/api_matchers/headers/base.rb +7 -7
  26. data/lib/api_matchers/headers/be_json.rb +2 -4
  27. data/lib/api_matchers/headers/be_xml.rb +2 -4
  28. data/lib/api_matchers/headers/have_cache_control.rb +90 -0
  29. data/lib/api_matchers/headers/have_cors_headers.rb +102 -0
  30. data/lib/api_matchers/headers/have_header.rb +102 -0
  31. data/lib/api_matchers/http_status/base.rb +48 -0
  32. data/lib/api_matchers/http_status/be_client_error.rb +17 -0
  33. data/lib/api_matchers/http_status/be_forbidden.rb +17 -0
  34. data/lib/api_matchers/http_status/be_no_content.rb +17 -0
  35. data/lib/api_matchers/http_status/be_not_found.rb +17 -0
  36. data/lib/api_matchers/http_status/be_redirect.rb +17 -0
  37. data/lib/api_matchers/http_status/be_server_error.rb +17 -0
  38. data/lib/api_matchers/http_status/be_successful.rb +17 -0
  39. data/lib/api_matchers/http_status/be_unauthorized.rb +17 -0
  40. data/lib/api_matchers/http_status/be_unprocessable.rb +17 -0
  41. data/lib/api_matchers/http_status/have_http_status.rb +39 -0
  42. data/lib/api_matchers/json_api/base.rb +83 -0
  43. data/lib/api_matchers/json_api/be_json_api_compliant.rb +158 -0
  44. data/lib/api_matchers/json_api/have_json_api_attributes.rb +62 -0
  45. data/lib/api_matchers/json_api/have_json_api_data.rb +110 -0
  46. data/lib/api_matchers/json_api/have_json_api_relationships.rb +62 -0
  47. data/lib/api_matchers/json_structure/base.rb +65 -0
  48. data/lib/api_matchers/json_structure/have_json_keys.rb +55 -0
  49. data/lib/api_matchers/json_structure/have_json_type.rb +72 -0
  50. data/lib/api_matchers/pagination/base.rb +73 -0
  51. data/lib/api_matchers/pagination/be_paginated.rb +73 -0
  52. data/lib/api_matchers/pagination/have_pagination_links.rb +74 -0
  53. data/lib/api_matchers/pagination/have_total_count.rb +77 -0
  54. data/lib/api_matchers/response_body/base.rb +10 -9
  55. data/lib/api_matchers/response_body/have_json.rb +2 -4
  56. data/lib/api_matchers/response_body/have_json_node.rb +45 -1
  57. data/lib/api_matchers/response_body/have_node.rb +2 -0
  58. data/lib/api_matchers/response_body/have_xml_node.rb +13 -14
  59. data/lib/api_matchers/response_body/match_json_schema.rb +89 -0
  60. data/lib/api_matchers/version.rb +3 -1
  61. data/lib/api_matchers.rb +75 -14
  62. data/spec/api_matchers/collection/be_sorted_by_spec.rb +110 -0
  63. data/spec/api_matchers/collection/have_json_size_spec.rb +101 -0
  64. data/spec/api_matchers/error_response/have_error_on_spec.rb +123 -0
  65. data/spec/api_matchers/error_response/have_error_spec.rb +108 -0
  66. data/spec/api_matchers/hateoas/have_link_spec.rb +105 -0
  67. data/spec/api_matchers/headers/base_spec.rb +8 -3
  68. data/spec/api_matchers/headers/be_json_spec.rb +1 -1
  69. data/spec/api_matchers/headers/be_xml_spec.rb +1 -1
  70. data/spec/api_matchers/headers/have_cache_control_spec.rb +102 -0
  71. data/spec/api_matchers/headers/have_cors_headers_spec.rb +74 -0
  72. data/spec/api_matchers/headers/have_header_spec.rb +88 -0
  73. data/spec/api_matchers/http_status/be_client_error_spec.rb +53 -0
  74. data/spec/api_matchers/http_status/be_forbidden_spec.rb +33 -0
  75. data/spec/api_matchers/http_status/be_no_content_spec.rb +33 -0
  76. data/spec/api_matchers/http_status/be_not_found_spec.rb +39 -0
  77. data/spec/api_matchers/http_status/be_redirect_spec.rb +55 -0
  78. data/spec/api_matchers/http_status/be_server_error_spec.rb +49 -0
  79. data/spec/api_matchers/http_status/be_successful_spec.rb +78 -0
  80. data/spec/api_matchers/http_status/be_unauthorized_spec.rb +33 -0
  81. data/spec/api_matchers/http_status/be_unprocessable_spec.rb +39 -0
  82. data/spec/api_matchers/http_status/have_http_status_spec.rb +81 -0
  83. data/spec/api_matchers/json_api/be_json_api_compliant_spec.rb +109 -0
  84. data/spec/api_matchers/json_api/have_json_api_attributes_spec.rb +61 -0
  85. data/spec/api_matchers/json_api/have_json_api_data_spec.rb +95 -0
  86. data/spec/api_matchers/json_api/have_json_api_relationships_spec.rb +61 -0
  87. data/spec/api_matchers/json_structure/have_json_keys_spec.rb +81 -0
  88. data/spec/api_matchers/json_structure/have_json_type_spec.rb +134 -0
  89. data/spec/api_matchers/pagination/be_paginated_spec.rb +95 -0
  90. data/spec/api_matchers/pagination/have_pagination_links_spec.rb +80 -0
  91. data/spec/api_matchers/pagination/have_total_count_spec.rb +85 -0
  92. data/spec/api_matchers/response_body/base_spec.rb +15 -7
  93. data/spec/api_matchers/response_body/have_json_node_spec.rb +57 -0
  94. data/spec/api_matchers/response_body/match_json_schema_spec.rb +86 -0
  95. metadata +152 -47
  96. data/.rvmrc.example +0 -1
  97. data/.travis.yml +0 -12
  98. data/Gemfile.lock +0 -46
  99. data/lib/api_matchers/http_status_code/base.rb +0 -32
  100. data/lib/api_matchers/http_status_code/be_bad_request.rb +0 -25
  101. data/lib/api_matchers/http_status_code/be_forbidden.rb +0 -21
  102. data/lib/api_matchers/http_status_code/be_internal_server_error.rb +0 -25
  103. data/lib/api_matchers/http_status_code/be_not_found.rb +0 -25
  104. data/lib/api_matchers/http_status_code/be_ok.rb +0 -25
  105. data/lib/api_matchers/http_status_code/be_unauthorized.rb +0 -25
  106. data/lib/api_matchers/http_status_code/be_unprocessable_entity.rb +0 -25
  107. data/lib/api_matchers/http_status_code/create_resource.rb +0 -25
  108. data/spec/api_matchers/http_status_code/base_spec.rb +0 -12
  109. data/spec/api_matchers/http_status_code/be_bad_request_spec.rb +0 -49
  110. data/spec/api_matchers/http_status_code/be_forbidden_spec.rb +0 -49
  111. data/spec/api_matchers/http_status_code/be_internal_server_error_spec.rb +0 -49
  112. data/spec/api_matchers/http_status_code/be_not_found_spec.rb +0 -49
  113. data/spec/api_matchers/http_status_code/be_ok_spec.rb +0 -49
  114. data/spec/api_matchers/http_status_code/be_unauthorized_spec.rb +0 -49
  115. data/spec/api_matchers/http_status_code/be_unprocessable_entity_spec.rb +0 -27
  116. 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
- let(:setup) { OpenStruct.new(:response_body_method => :body) }
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 "should read from the initialize" do
17
- expect(subject.setup).to equal setup
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
- let(:setup) { OpenStruct.new(:response_body_method => nil) }
39
- subject { APIMatchers::ResponseBody::Base.new(setup: setup, expected_node: :status) }
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