shaf 0.1.0.beta
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/bin/shaf +57 -0
- data/lib/shaf.rb +9 -0
- data/lib/shaf/api_doc.rb +124 -0
- data/lib/shaf/api_doc/comment.rb +27 -0
- data/lib/shaf/api_doc/document.rb +133 -0
- data/lib/shaf/app.rb +22 -0
- data/lib/shaf/command.rb +42 -0
- data/lib/shaf/command/console.rb +17 -0
- data/lib/shaf/command/generate.rb +19 -0
- data/lib/shaf/command/new.rb +79 -0
- data/lib/shaf/command/server.rb +15 -0
- data/lib/shaf/command/templates/Gemfile.erb +30 -0
- data/lib/shaf/doc_model.rb +54 -0
- data/lib/shaf/errors.rb +77 -0
- data/lib/shaf/extensions.rb +11 -0
- data/lib/shaf/extensions/authorize.rb +42 -0
- data/lib/shaf/extensions/resource_uris.rb +153 -0
- data/lib/shaf/formable.rb +188 -0
- data/lib/shaf/generator.rb +69 -0
- data/lib/shaf/generator/controller.rb +106 -0
- data/lib/shaf/generator/migration.rb +122 -0
- data/lib/shaf/generator/migration/add_column.rb +49 -0
- data/lib/shaf/generator/migration/create_table.rb +40 -0
- data/lib/shaf/generator/migration/drop_column.rb +45 -0
- data/lib/shaf/generator/migration/empty.rb +21 -0
- data/lib/shaf/generator/migration/rename_column.rb +48 -0
- data/lib/shaf/generator/model.rb +68 -0
- data/lib/shaf/generator/policy.rb +43 -0
- data/lib/shaf/generator/scaffold.rb +26 -0
- data/lib/shaf/generator/serializer.rb +258 -0
- data/lib/shaf/generator/templates/api/controller.rb.erb +62 -0
- data/lib/shaf/generator/templates/api/model.rb.erb +20 -0
- data/lib/shaf/generator/templates/api/policy.rb.erb +26 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +24 -0
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +98 -0
- data/lib/shaf/generator/templates/spec/model.rb.erb +40 -0
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +46 -0
- data/lib/shaf/helpers.rb +15 -0
- data/lib/shaf/helpers/json_html.rb +65 -0
- data/lib/shaf/helpers/paginate.rb +24 -0
- data/lib/shaf/helpers/payload.rb +115 -0
- data/lib/shaf/helpers/session.rb +53 -0
- data/lib/shaf/middleware.rb +1 -0
- data/lib/shaf/middleware/request_id.rb +16 -0
- data/lib/shaf/registrable_factory.rb +71 -0
- data/lib/shaf/settings.rb +33 -0
- data/lib/shaf/spec.rb +6 -0
- data/lib/shaf/spec/http_method_utils.rb +24 -0
- data/lib/shaf/spec/integration_spec.rb +53 -0
- data/lib/shaf/spec/model.rb +17 -0
- data/lib/shaf/spec/payload_test.rb +78 -0
- data/lib/shaf/spec/payload_utils.rb +176 -0
- data/lib/shaf/spec/serializer_spec.rb +24 -0
- data/lib/shaf/tasks.rb +4 -0
- data/lib/shaf/tasks/db.rb +61 -0
- data/lib/shaf/tasks/test.rb +43 -0
- data/lib/shaf/utils.rb +53 -0
- data/lib/shaf/version.rb +3 -0
- data/templates/Rakefile +13 -0
- data/templates/api/controllers/base_controller.rb +57 -0
- data/templates/api/controllers/docs_controller.rb +16 -0
- data/templates/api/controllers/root_controller.rb +8 -0
- data/templates/api/serializers/error_serializer.rb +10 -0
- data/templates/api/serializers/form_serializer.rb +42 -0
- data/templates/api/serializers/root_serializer.rb +16 -0
- data/templates/config.ru +4 -0
- data/templates/config/bootstrap.rb +12 -0
- data/templates/config/constants.rb +5 -0
- data/templates/config/customize.rb +3 -0
- data/templates/config/database.rb +40 -0
- data/templates/config/directories.rb +32 -0
- data/templates/config/helpers.rb +18 -0
- data/templates/config/initializers.rb +12 -0
- data/templates/config/initializers/db_migrations.rb +18 -0
- data/templates/config/initializers/hal_presenter.rb +6 -0
- data/templates/config/initializers/logging.rb +7 -0
- data/templates/config/initializers/sequel.rb +4 -0
- data/templates/config/settings.yml +19 -0
- data/templates/frontend/assets/css/main.css +70 -0
- data/templates/frontend/views/form.erb +16 -0
- data/templates/frontend/views/layout.erb +11 -0
- data/templates/frontend/views/payload.erb +8 -0
- data/templates/spec/integration/root_spec.rb +14 -0
- data/templates/spec/serializers/root_serializer_spec.rb +12 -0
- data/templates/spec/spec_helper.rb +4 -0
- metadata +348 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Session
|
5
|
+
|
6
|
+
SESSION_TTL = 60 * 60 * 24 * 2 # 2 days
|
7
|
+
|
8
|
+
def login(email, password)
|
9
|
+
return unless email && password
|
10
|
+
user = User.first(email: email) or return
|
11
|
+
bcrypt = BCrypt::Password.new(user.password_digest)
|
12
|
+
return unless bcrypt == password
|
13
|
+
@current_user = user
|
14
|
+
|
15
|
+
Session.where(user_id: user.id).delete
|
16
|
+
params = {
|
17
|
+
user_id: user.id,
|
18
|
+
expire_at: Time.now + SESSION_TTL,
|
19
|
+
}
|
20
|
+
Session.create(params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def extend_session(session)
|
24
|
+
return unless session
|
25
|
+
session.update(expire_at: Time.now + SESSION_TTL)
|
26
|
+
session.auth_token = request.env['HTTP_X_AUTH_TOKEN']
|
27
|
+
session
|
28
|
+
end
|
29
|
+
|
30
|
+
def logout
|
31
|
+
current_session&.destroy
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_user
|
35
|
+
unless defined?(@current_user) && @current_user
|
36
|
+
return unless request.env.key? 'HTTP_X_AUTH_TOKEN'
|
37
|
+
digest = Digest::SHA256.hexdigest(request.env['HTTP_X_AUTH_TOKEN'])
|
38
|
+
session = Session.where(auth_token_digest: digest).first
|
39
|
+
@current_user = User[session.user_id] if session&.valid?
|
40
|
+
end
|
41
|
+
@current_user
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_session
|
45
|
+
unless @current_session
|
46
|
+
return unless current_user
|
47
|
+
@current_session = Session.where(user_id: current_user.id).first
|
48
|
+
end
|
49
|
+
@current_session
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'shaf/middleware/request_id'
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Shaf
|
2
|
+
module RegistrableFactory
|
3
|
+
|
4
|
+
class NotFoundError < StandardError; end
|
5
|
+
|
6
|
+
def all
|
7
|
+
reg.dup
|
8
|
+
end
|
9
|
+
|
10
|
+
def size
|
11
|
+
reg.size
|
12
|
+
end
|
13
|
+
|
14
|
+
def register(clazz)
|
15
|
+
reg << clazz
|
16
|
+
end
|
17
|
+
|
18
|
+
def unregister(*str)
|
19
|
+
return if str.empty? || !str.all?
|
20
|
+
reg.delete_if { |clazz| matching_class? str, clazz }
|
21
|
+
end
|
22
|
+
|
23
|
+
def lookup(*str)
|
24
|
+
return if str.empty? || !str.all?
|
25
|
+
reg.detect { |clazz| matching_class? str, clazz }
|
26
|
+
end
|
27
|
+
|
28
|
+
def usage
|
29
|
+
reg.compact.map do |entry|
|
30
|
+
usage = entry.instance_eval { @usage }
|
31
|
+
usage.respond_to?(:call) ? usage.call : usage
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create(*params)
|
36
|
+
clazz = lookup(*params)
|
37
|
+
raise NotFoundError.new(%Q(Command '#{ARGV}' is not supported)) unless clazz
|
38
|
+
|
39
|
+
args = init_args(clazz, params)
|
40
|
+
clazz.new(*args)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def reg
|
46
|
+
@reg ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
def matching_class?(strings, clazz)
|
50
|
+
identifiers = clazz.instance_eval { @identifiers }
|
51
|
+
return false if strings.size < identifiers.size
|
52
|
+
identifiers.zip(strings).all? { |pattern, str| matching_identifier? str, pattern }
|
53
|
+
end
|
54
|
+
|
55
|
+
def matching_identifier?(str, pattern)
|
56
|
+
return false if pattern.nil? || str.nil? || str.empty?
|
57
|
+
pattern = pattern.to_s if pattern.is_a? Symbol
|
58
|
+
return str == pattern if pattern.is_a? String
|
59
|
+
str.match(pattern) || false
|
60
|
+
end
|
61
|
+
|
62
|
+
def identifier_count(clazz)
|
63
|
+
clazz.instance_eval { @identifiers }&.size || 0
|
64
|
+
end
|
65
|
+
|
66
|
+
def init_args(clazz, params)
|
67
|
+
first_non_id = identifier_count(clazz)
|
68
|
+
params[first_non_id..-1]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
|
4
|
+
module Shaf
|
5
|
+
class Settings
|
6
|
+
SETTINGS_FILE = 'config/settings.yml'
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def load
|
10
|
+
@settings = File.exist?(SETTINGS_FILE) ?
|
11
|
+
YAML.load(File.read(SETTINGS_FILE)) : {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def env
|
15
|
+
@env ||= (ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args)
|
19
|
+
load unless defined? @settings
|
20
|
+
|
21
|
+
define_singleton_method(method) do
|
22
|
+
@settings.dig(env.to_s, method.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
return public_send(method)
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(method, include_private = false)
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/shaf/spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Spec
|
3
|
+
module HttpMethodUtils
|
4
|
+
include ::Rack::Test::Methods
|
5
|
+
|
6
|
+
[:get, :put, :post, :delete].each do |m|
|
7
|
+
define_method m do |*args|
|
8
|
+
set_headers
|
9
|
+
super(*args)
|
10
|
+
set_payload parse_response(last_response.body)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def status
|
15
|
+
last_response&.status
|
16
|
+
end
|
17
|
+
|
18
|
+
def headers
|
19
|
+
last_response&.headers
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Shaf
|
2
|
+
module Spec
|
3
|
+
class IntegrationSpec < Minitest::Spec
|
4
|
+
include Minitest::Hooks
|
5
|
+
include HttpMethodUtils
|
6
|
+
include PayloadUtils
|
7
|
+
include UriHelper
|
8
|
+
|
9
|
+
TRANSACTION_OPTIONS = {
|
10
|
+
rollback: :always,
|
11
|
+
savepoint: true,
|
12
|
+
auto_savepoint: true
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
register_spec_type self do |desc, args|
|
16
|
+
next unless args && args.is_a?(Hash)
|
17
|
+
args[:type]&.to_s == 'integration'
|
18
|
+
end
|
19
|
+
|
20
|
+
around do |&block|
|
21
|
+
DB.transaction(TRANSACTION_OPTIONS) { super(&block) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_headers
|
25
|
+
if defined?(@__integration_test_auth_token) && @__integration_test_auth_token
|
26
|
+
header 'X-AUTH-TOKEN', @__integration_test_auth_token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_response(body)
|
31
|
+
return nil if body.empty?
|
32
|
+
JSON.parse(body, symbolize_names: true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def app
|
36
|
+
App.instance
|
37
|
+
end
|
38
|
+
|
39
|
+
# def login(email, pass)
|
40
|
+
# params = {email: email, password: pass}
|
41
|
+
# header 'Content-Type', 'application/json'
|
42
|
+
# post Shaf::UriHelper.session_uri, JSON.generate(params)
|
43
|
+
# @__integration_test_auth_token = attribute[:auth_token]
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# def logout
|
47
|
+
# delete Shaf::UriHelper.session_uri
|
48
|
+
# @__integration_test_auth_token = nil
|
49
|
+
# end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
$:.unshift '.'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'test/test_utils/payload'
|
4
|
+
|
5
|
+
class PayloadTest < Minitest::Test
|
6
|
+
include TestUtils::Payload
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@payload = {
|
10
|
+
attr1: 1,
|
11
|
+
attr2: 2,
|
12
|
+
_links: {
|
13
|
+
link1: {
|
14
|
+
href: '/link1'
|
15
|
+
},
|
16
|
+
link2: {
|
17
|
+
href: '/link2'
|
18
|
+
}
|
19
|
+
},
|
20
|
+
_embedded: {
|
21
|
+
embed1: {
|
22
|
+
attr_e1: 'e1',
|
23
|
+
_links: {
|
24
|
+
link_e1: {
|
25
|
+
href: '/links/e1'
|
26
|
+
}
|
27
|
+
},
|
28
|
+
_embedded: {
|
29
|
+
embed1a: {
|
30
|
+
attr_e1a: 'e1a'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
},
|
34
|
+
embed2: [
|
35
|
+
{
|
36
|
+
attr_embed2a: 'e2a',
|
37
|
+
_links: {
|
38
|
+
link_e2a: {
|
39
|
+
href: 'links/e2a'
|
40
|
+
}
|
41
|
+
}
|
42
|
+
},
|
43
|
+
{
|
44
|
+
attr_embed2b: 'e2b',
|
45
|
+
_links: {
|
46
|
+
link_e2b: {
|
47
|
+
href: 'links/e2b'
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
51
|
+
]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
#TODO How to verify that we do get assertions
|
57
|
+
|
58
|
+
def test_links
|
59
|
+
assert_link :link1, '/link1'
|
60
|
+
assert_link :link2, '/link2'
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_embedded_without_block
|
64
|
+
assert_equal @payload[:_embedded], embedded
|
65
|
+
assert_equal @payload[:_embedded][:embed1], embedded(:embed1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_embedded_with_block
|
69
|
+
embedded :embed1 do
|
70
|
+
assert_attribute :attr_e1, 'e1'
|
71
|
+
assert_link :link_e1, '/links/e1'
|
72
|
+
|
73
|
+
embedded :embed1a do
|
74
|
+
assert_attribute :attr_e1a, 'e1a'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'minitest/assertions'
|
2
|
+
|
3
|
+
module Shaf
|
4
|
+
module Spec
|
5
|
+
module PayloadUtils
|
6
|
+
|
7
|
+
class Embedded
|
8
|
+
include HttpMethodUtils
|
9
|
+
include PayloadUtils
|
10
|
+
include Minitest::Assertions
|
11
|
+
|
12
|
+
# This is needed by Minitest::Assertions
|
13
|
+
# And we need that module to have all assert_*/refute_* methods
|
14
|
+
# available in this class
|
15
|
+
attr_accessor :assertions
|
16
|
+
|
17
|
+
def initialize(payload, context, block)
|
18
|
+
@payload = payload
|
19
|
+
@context = context
|
20
|
+
@block = block
|
21
|
+
@assertions = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
instance_exec(&@block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(method, *args, &block)
|
29
|
+
if @context&.respond_to? method
|
30
|
+
define_singleton_method(method) { |*a, &b| @context.public_send method, *a, &b }
|
31
|
+
return public_send(method, *args, &block)
|
32
|
+
end
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def respond_to_missing?(method, include_private = false)
|
37
|
+
return true if @context&.respond_to? method
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def set_payload(payload)
|
44
|
+
@payload = payload
|
45
|
+
@payload = JSON.parse(payload, symbolize_names: true) if payload.is_a?(String)
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_payload
|
49
|
+
refute @payload.nil?, "No previous response body"
|
50
|
+
@payload
|
51
|
+
end
|
52
|
+
|
53
|
+
def attributes
|
54
|
+
last_payload.reject { |key,_| [:_links, :_embedded].include? key }
|
55
|
+
end
|
56
|
+
|
57
|
+
def links
|
58
|
+
last_payload[:_links] || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def link_rels
|
62
|
+
links.keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def embedded_resources
|
66
|
+
last_payload[:_embedded]&.keys || []
|
67
|
+
end
|
68
|
+
|
69
|
+
def embedded(name = nil)
|
70
|
+
assert_has_embedded name
|
71
|
+
keys = [:_embedded, name&.to_sym].compact
|
72
|
+
return last_payload.dig(*keys) unless block_given?
|
73
|
+
Embedded.new(last_payload.dig(*keys), self, Proc.new).call
|
74
|
+
end
|
75
|
+
|
76
|
+
def follow_rel(rel, method: nil)
|
77
|
+
assert_has_link(rel)
|
78
|
+
link = links[rel.to_sym]
|
79
|
+
if method && respond_to?(method)
|
80
|
+
public_send(method, link[:href])
|
81
|
+
else
|
82
|
+
get link[:href]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def fill_form(fields)
|
87
|
+
fields.map do |field|
|
88
|
+
value = case field[:type]
|
89
|
+
when 'integer'
|
90
|
+
field[:name].size
|
91
|
+
when 'string'
|
92
|
+
"value for #{field[:name]}"
|
93
|
+
else
|
94
|
+
"type not supported"
|
95
|
+
end
|
96
|
+
[field[:name], value]
|
97
|
+
end.to_h
|
98
|
+
end
|
99
|
+
|
100
|
+
def assert_status(code)
|
101
|
+
assert_equal code, status,
|
102
|
+
"Response status was expected to be #{code}."
|
103
|
+
end
|
104
|
+
|
105
|
+
def assert_header(key, value)
|
106
|
+
assert_equal value, headers[key],
|
107
|
+
"Response was expected have header #{key} = #{value}."
|
108
|
+
end
|
109
|
+
|
110
|
+
def assert_has_attribute(attr)
|
111
|
+
assert last_payload[attr.to_sym],
|
112
|
+
"Response does not contain attribute '#{attr}': #{last_payload}"
|
113
|
+
end
|
114
|
+
|
115
|
+
def refute_has_attribute(attr)
|
116
|
+
refute last_payload[attr.to_sym],
|
117
|
+
"Response contains disallowed attribute '#{attr}': #{last_payload}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def assert_has_attributes(*attrs)
|
121
|
+
attrs.each { |attr| assert_has_attribute(attr) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def refute_has_attributes(*attrs)
|
125
|
+
attrs.each { |attr| refute_has_attribute(attr) }
|
126
|
+
end
|
127
|
+
|
128
|
+
def assert_attribute(attr, expected)
|
129
|
+
assert_has_attribute(attr)
|
130
|
+
assert_equal expected, last_payload[attr.to_sym]
|
131
|
+
end
|
132
|
+
|
133
|
+
def assert_has_link(rel)
|
134
|
+
assert last_payload.key?(:_links), "Response does not have any links: #{last_payload}"
|
135
|
+
assert last_payload[:_links][rel.to_sym],
|
136
|
+
"Response does not contain link with rel '#{rel}': #{last_payload}"
|
137
|
+
assert last_payload[:_links][rel.to_sym][:href],
|
138
|
+
"link with rel '#{rel}' in ressponse does not have a href: #{last_payload}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def refute_has_link(rel)
|
142
|
+
refute last_payload.dig(:_links, rel.to_sym),
|
143
|
+
"Response contains disallowed link with rel '#{rel}': #{last_payload}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def assert_has_links(*rels)
|
147
|
+
rels.each { |rel| assert_has_link(rel) }
|
148
|
+
end
|
149
|
+
|
150
|
+
def refute_has_links(*rels)
|
151
|
+
rels.each { |rel| refute_has_link(rel) }
|
152
|
+
end
|
153
|
+
|
154
|
+
def assert_link(rel, expected)
|
155
|
+
assert_has_link(rel)
|
156
|
+
assert_equal expected, last_payload.dig(:_links, rel.to_sym, :href)
|
157
|
+
end
|
158
|
+
|
159
|
+
def assert_has_embedded(*names)
|
160
|
+
names.each do |name|
|
161
|
+
assert last_payload.key?(:_embedded),
|
162
|
+
"Response does not have any embedded resources: #{last_payload}"
|
163
|
+
assert last_payload[:_embedded][name.to_sym],
|
164
|
+
"Response does not contain embedded resource with name '#{name}': #{last_payload}"
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def refute_has_embedded(*names)
|
169
|
+
names.each do |name|
|
170
|
+
refute last_payload.dig(:_embedded, name.to_sym),
|
171
|
+
"Response contains disallowed embedded resource with name '#{name}': #{last_payload}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|