jsonapi_rspec 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|