jsonapi_rspec 0.2.1 → 0.2.2
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/jsonapi_rspec.rb +65 -0
- data/lib/jsonapi_rspec/be_json_api_response.rb +82 -0
- data/lib/jsonapi_rspec/be_json_api_response_for.rb +13 -53
- data/lib/jsonapi_rspec/failure_messages.rb +16 -0
- data/lib/jsonapi_rspec/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f6a700c0770ee5ba9954b77fcc7818dce6274ae
|
4
|
+
data.tar.gz: cbd8512415d501c272add052d2b7ae9668ab5ca4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a59ac4bda91aa5f2ec4c41096779803f1266c9e9d509a22c977b9d2b39145957864da2b8b0a8fb2a3948de1e24654f7e12084da0bbd1803375af8d54a4e783d
|
7
|
+
data.tar.gz: 9ecc45dad3f2f86d63c992c8c8aff538125c8d9e1a3f2241b5bb740c78aee641c388f036c3fa466a7212d5d9b6a346f371f4cd6ee7afaad6f2a193ca142e2dbd
|
data/Gemfile.lock
CHANGED
data/lib/jsonapi_rspec.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'jsonapi_rspec/version'
|
2
|
+
require 'jsonapi_rspec/failure_messages'
|
3
|
+
require 'jsonapi_rspec/be_json_api_response'
|
2
4
|
require 'jsonapi_rspec/be_json_api_response_for'
|
3
5
|
|
4
6
|
module JsonapiRspec
|
@@ -15,6 +17,69 @@ module JsonapiRspec
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
20
|
+
def failure_message
|
21
|
+
"#{@failure_message} - parsed response: #{pretty_response}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def failure_message_when_negated
|
25
|
+
@failure_message = "handle method 'failure_message_when_negated' in custom_matchers.rb"
|
26
|
+
"#{@failure_message}: #{pretty_response}"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def pretty_response
|
32
|
+
JSON.pretty_generate(@parsed_response)
|
33
|
+
rescue JSON::GeneratorError
|
34
|
+
@parsed_response.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_response?(response)
|
38
|
+
return set_failure_message(FailureMessages::EMPTY) if response.body == ''
|
39
|
+
return set_failure_message(FailureMessages::NIL) if response.body.nil?
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def required_top_level_sections?
|
44
|
+
valid = @parsed_response.dig('data') || @parsed_response.dig('errors') || @parsed_response.dig('meta')
|
45
|
+
return set_failure_message(FailureMessages::MISSING_REQ_TOP_LVL) unless valid
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def conflicting_sections?
|
50
|
+
conflicting = false
|
51
|
+
if @parsed_response.dig('included')
|
52
|
+
# must have a data section
|
53
|
+
if @parsed_response.dig('data').nil?
|
54
|
+
conflicting = true
|
55
|
+
set_failure_message(FailureMessages::CONFLICTING_TOP_LVL)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
conflicting
|
59
|
+
end
|
60
|
+
|
61
|
+
def valid_meta_section?
|
62
|
+
meta = @parsed_response.dig('meta')
|
63
|
+
return set_failure_message(FailureMessages::MISSING_META) unless meta.is_a?(Hash)
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
def response_is_error?
|
68
|
+
is_error = !@parsed_response.dig('errors').nil?
|
69
|
+
set_failure_message(FailureMessages::ERROR) if is_error
|
70
|
+
is_error
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_data_section?
|
74
|
+
data_section = @parsed_response.dig('data')
|
75
|
+
valid = data_section.is_a?(Hash) || data_section.is_a?(Array)
|
76
|
+
unless valid
|
77
|
+
return set_failure_message(FailureMessages::INVALID_DATA_SECTION)
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
# Autoload Section
|
18
83
|
autoload :Configuration, 'jsonapi_rspec/configuration'
|
19
84
|
end
|
20
85
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rspec/matchers'
|
2
|
+
|
3
|
+
require_relative 'string'
|
4
|
+
|
5
|
+
# Class BeJsonApiResponse provides custom RSpec matching for json:api
|
6
|
+
# responses in general.
|
7
|
+
#
|
8
|
+
# It expects a Rack::Response (or similar) response object
|
9
|
+
#
|
10
|
+
# Usage:
|
11
|
+
# expect(response).to BeJsonApiResponse.new
|
12
|
+
#
|
13
|
+
# @author Chris Blackburn <87a1779b@opayq.com>
|
14
|
+
#
|
15
|
+
class BeJsonApiResponse
|
16
|
+
include JsonapiRspec
|
17
|
+
|
18
|
+
def matches?(response)
|
19
|
+
return false unless valid_response?(response)
|
20
|
+
|
21
|
+
@parsed_response = JSON.parse(response.body)
|
22
|
+
|
23
|
+
return false if response_is_error?
|
24
|
+
return false unless required_top_level_sections?
|
25
|
+
return false if conflicting_sections?
|
26
|
+
|
27
|
+
if JsonapiRspec.configuration.meta_required
|
28
|
+
return false unless valid_meta_section?
|
29
|
+
end
|
30
|
+
|
31
|
+
@parsed_response.each_key do |key|
|
32
|
+
case key.to_sym
|
33
|
+
when :data
|
34
|
+
return false unless valid_data_section?
|
35
|
+
when :meta
|
36
|
+
return false unless valid_meta_section?
|
37
|
+
when :jsonapi
|
38
|
+
next # this can legally be anything
|
39
|
+
when :included
|
40
|
+
next # TODO: handle included objects
|
41
|
+
when :links
|
42
|
+
next # TODO: handle links objects
|
43
|
+
else
|
44
|
+
return set_failure_message(FailureMessages::UNEXPECTED_TOP_LVL_KEY % key)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Set the failure message
|
54
|
+
#
|
55
|
+
# @param [String] msg Failure message
|
56
|
+
#
|
57
|
+
# @return [Boolean] always returns false
|
58
|
+
#
|
59
|
+
def set_failure_message(msg)
|
60
|
+
@failure_message = "#{FailureMessages::GENERAL_PREFIX} #{msg}"
|
61
|
+
false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Usage:
|
66
|
+
# expect(response).to be_jsonapi_response
|
67
|
+
#
|
68
|
+
RSpec::Matchers.define :be_jsonapi_response do
|
69
|
+
match do |actual_response|
|
70
|
+
@instance = BeJsonApiResponse.new
|
71
|
+
|
72
|
+
def failure_message
|
73
|
+
@instance.failure_message
|
74
|
+
end
|
75
|
+
|
76
|
+
def failure_message_when_negated
|
77
|
+
@instance.failure_message
|
78
|
+
end
|
79
|
+
|
80
|
+
@instance.matches?(actual_response)
|
81
|
+
end
|
82
|
+
end
|
@@ -16,6 +16,8 @@ require_relative 'string'
|
|
16
16
|
# @author Chris Blackburn <87a1779b@opayq.com>
|
17
17
|
#
|
18
18
|
class BeJsonApiResponseFor
|
19
|
+
include JsonapiRspec
|
20
|
+
|
19
21
|
def initialize(object_instance, plural_form = nil)
|
20
22
|
@object_instance = object_instance
|
21
23
|
@plural_form = plural_form
|
@@ -42,32 +44,18 @@ class BeJsonApiResponseFor
|
|
42
44
|
next # this can legally be anything
|
43
45
|
when :included
|
44
46
|
next # TODO: handle included objects
|
47
|
+
when :links
|
48
|
+
next # TODO: handle links objects
|
45
49
|
else
|
46
|
-
return set_failure_message(
|
50
|
+
return set_failure_message(FailureMessages::UNEXPECTED_TOP_LVL_KEY % key)
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
50
54
|
true
|
51
55
|
end
|
52
56
|
|
53
|
-
def failure_message
|
54
|
-
@failure_message ||= "Expected object [#{@object_instance}] to match"
|
55
|
-
"#{@failure_message} - parsed response: #{pretty_response}"
|
56
|
-
end
|
57
|
-
|
58
|
-
def failure_message_when_negated
|
59
|
-
@failure_message = "handle method 'failure_message_when_negated' in custom_matchers.rb"
|
60
|
-
"#{@failure_message}: #{pretty_response}"
|
61
|
-
end
|
62
|
-
|
63
57
|
private
|
64
58
|
|
65
|
-
def pretty_response
|
66
|
-
JSON.pretty_generate(@parsed_response)
|
67
|
-
rescue JSON::GeneratorError
|
68
|
-
@parsed_response.to_s
|
69
|
-
end
|
70
|
-
|
71
59
|
# Set the failure message
|
72
60
|
#
|
73
61
|
# @param [String] msg Failure message
|
@@ -75,49 +63,21 @@ class BeJsonApiResponseFor
|
|
75
63
|
# @return [Boolean] always returns false
|
76
64
|
#
|
77
65
|
def set_failure_message(msg)
|
78
|
-
@failure_message = msg
|
66
|
+
@failure_message = "#{FailureMessages::OBJECT_PREFIX} #{msg}"
|
79
67
|
false
|
80
68
|
end
|
81
69
|
|
82
|
-
def valid_response?(response)
|
83
|
-
if response.body == ''
|
84
|
-
return set_failure_message('Expected response to match an object instance but it is an empty string')
|
85
|
-
end
|
86
|
-
true
|
87
|
-
end
|
88
|
-
|
89
|
-
def valid_data_section?
|
90
|
-
unless @parsed_response.dig('data').is_a?(Hash)
|
91
|
-
return set_failure_message("The 'data' section is missing or invalid")
|
92
|
-
end
|
93
|
-
true
|
94
|
-
end
|
95
|
-
|
96
70
|
def valid_type?(data_type)
|
97
71
|
object_type = @plural_form ||
|
98
72
|
@object_instance.class.name.pluralize.underscore.dasherize
|
99
73
|
unless data_type == object_type
|
100
|
-
return set_failure_message(
|
101
|
-
|
102
|
-
|
103
|
-
end
|
104
|
-
|
105
|
-
def valid_meta_section?
|
106
|
-
meta = @parsed_response.dig('meta')
|
107
|
-
return set_failure_message("The 'meta' section is missing or invalid") unless meta.is_a?(Hash)
|
108
|
-
return set_failure_message("The 'meta:version' is missing") if meta.dig('version').nil?
|
109
|
-
unless meta.dig('copyright') =~ /^Copyright.+\d{4}/
|
110
|
-
return set_failure_message("The 'meta:copyright' is missing or invalid - regex: '/^Copyright.+\\d{4}/'")
|
74
|
+
return set_failure_message(
|
75
|
+
format(FailureMessages::DATA_TYPE_MISMATCH, data_type, object_type)
|
76
|
+
)
|
111
77
|
end
|
112
78
|
true
|
113
79
|
end
|
114
80
|
|
115
|
-
def response_is_error?
|
116
|
-
is_error = !@parsed_response.dig('errors').nil?
|
117
|
-
set_failure_message('Response is an error') if is_error
|
118
|
-
is_error
|
119
|
-
end
|
120
|
-
|
121
81
|
def match_attribute?(attr_name, json_val)
|
122
82
|
obj_val = @object_instance.send(attr_name.to_sym)
|
123
83
|
obj_val_class_name = obj_val.class.name
|
@@ -129,10 +89,8 @@ class BeJsonApiResponseFor
|
|
129
89
|
matched = obj_val.to_i == DateTime.parse(json_val).to_i
|
130
90
|
when 'Time'
|
131
91
|
matched = obj_val.to_i == Time.parse(json_val).to_i
|
132
|
-
when 'String', 'NilClass', 'TrueClass', 'FalseClass', 'Fixnum', 'Integer', 'Bignum'
|
133
|
-
matched = obj_val == json_val
|
134
92
|
else
|
135
|
-
|
93
|
+
matched = obj_val == json_val
|
136
94
|
end
|
137
95
|
|
138
96
|
unless matched
|
@@ -153,7 +111,9 @@ class BeJsonApiResponseFor
|
|
153
111
|
when :id
|
154
112
|
object_id = @object_instance.send(key)
|
155
113
|
unless object_id == value.to_i
|
156
|
-
return set_failure_message(
|
114
|
+
return set_failure_message(
|
115
|
+
format(FailureMessages::OBJECT_ID_MISMATCH, value, object_id)
|
116
|
+
)
|
157
117
|
end
|
158
118
|
when :type
|
159
119
|
return false unless valid_type?(value)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module FailureMessages
|
2
|
+
GENERAL_PREFIX = 'Expected a json:api compliant success response but'.freeze
|
3
|
+
OBJECT_PREFIX = 'Expected a json:api response for an object instance but'.freeze
|
4
|
+
|
5
|
+
ERROR = 'it is an error'.freeze
|
6
|
+
EMPTY = 'it is empty'.freeze
|
7
|
+
NIL = 'it is nil'.freeze
|
8
|
+
|
9
|
+
MISSING_REQ_TOP_LVL = 'it is missing a required top-level section'.freeze
|
10
|
+
CONFLICTING_TOP_LVL = "it cannot contain 'included' without a 'data' section".freeze
|
11
|
+
UNEXPECTED_TOP_LVL_KEY = "it has an unexpected key: '%s'".freeze
|
12
|
+
INVALID_DATA_SECTION = "the 'data' section must be a Hash or an Array".freeze
|
13
|
+
DATA_TYPE_MISMATCH = "data:type '%s' doesn't match: '%s'".freeze
|
14
|
+
OBJECT_ID_MISMATCH = "data:id '%s' doesn't match object id: '%s'".freeze
|
15
|
+
MISSING_META = "the 'meta' section is missing or invalid".freeze
|
16
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi_rspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Blackburn
|
@@ -141,8 +141,10 @@ files:
|
|
141
141
|
- bin/setup
|
142
142
|
- jsonapi_rspec.gemspec
|
143
143
|
- lib/jsonapi_rspec.rb
|
144
|
+
- lib/jsonapi_rspec/be_json_api_response.rb
|
144
145
|
- lib/jsonapi_rspec/be_json_api_response_for.rb
|
145
146
|
- lib/jsonapi_rspec/configuration.rb
|
147
|
+
- lib/jsonapi_rspec/failure_messages.rb
|
146
148
|
- lib/jsonapi_rspec/string.rb
|
147
149
|
- lib/jsonapi_rspec/version.rb
|
148
150
|
homepage: https://github.com/midwire/jsonapi_rspec
|