cuprum-rails 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +145 -0
- data/DEVELOPMENT.md +20 -0
- data/README.md +356 -63
- data/lib/cuprum/rails/action.rb +32 -16
- data/lib/cuprum/rails/actions/create.rb +62 -15
- data/lib/cuprum/rails/actions/destroy.rb +23 -7
- data/lib/cuprum/rails/actions/edit.rb +23 -7
- data/lib/cuprum/rails/actions/index.rb +30 -10
- data/lib/cuprum/rails/actions/middleware/associations/cache.rb +112 -0
- data/lib/cuprum/rails/actions/middleware/associations/find.rb +23 -0
- data/lib/cuprum/rails/actions/middleware/associations/parent.rb +70 -0
- data/lib/cuprum/rails/actions/middleware/associations/query.rb +140 -0
- data/lib/cuprum/rails/actions/middleware/associations.rb +12 -0
- data/lib/cuprum/rails/actions/middleware/log_request.rb +126 -0
- data/lib/cuprum/rails/actions/middleware/log_result.rb +51 -0
- data/lib/cuprum/rails/actions/middleware/resources/find.rb +44 -0
- data/lib/cuprum/rails/actions/middleware/resources/query.rb +91 -0
- data/lib/cuprum/rails/actions/middleware/resources.rb +11 -0
- data/lib/cuprum/rails/actions/middleware.rb +13 -0
- data/lib/cuprum/rails/actions/new.rb +16 -4
- data/lib/cuprum/rails/actions/parameter_validation.rb +60 -0
- data/lib/cuprum/rails/actions/resource_action.rb +119 -42
- data/lib/cuprum/rails/actions/show.rb +23 -7
- data/lib/cuprum/rails/actions/update.rb +70 -22
- data/lib/cuprum/rails/actions.rb +11 -7
- data/lib/cuprum/rails/collection.rb +27 -47
- data/lib/cuprum/rails/command.rb +3 -1
- data/lib/cuprum/rails/commands/destroy_one.rb +10 -6
- data/lib/cuprum/rails/commands/find_many.rb +8 -1
- data/lib/cuprum/rails/commands/find_matching.rb +1 -1
- data/lib/cuprum/rails/commands/find_one.rb +8 -0
- data/lib/cuprum/rails/commands/insert_one.rb +17 -6
- data/lib/cuprum/rails/commands/update_one.rb +16 -5
- data/lib/cuprum/rails/constraints/parameters_contract.rb +14 -0
- data/lib/cuprum/rails/constraints.rb +10 -0
- data/lib/cuprum/rails/controller.rb +12 -2
- data/lib/cuprum/rails/controllers/action.rb +100 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +33 -7
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +36 -0
- data/lib/cuprum/rails/controllers/class_methods/middleware.rb +88 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +2 -2
- data/lib/cuprum/rails/controllers/configuration.rb +41 -1
- data/lib/cuprum/rails/controllers/middleware.rb +59 -0
- data/lib/cuprum/rails/controllers.rb +2 -0
- data/lib/cuprum/rails/errors/invalid_parameters.rb +55 -0
- data/lib/cuprum/rails/errors/invalid_statement.rb +11 -0
- data/lib/cuprum/rails/errors/missing_parameter.rb +42 -0
- data/lib/cuprum/rails/errors/resource_error.rb +46 -0
- data/lib/cuprum/rails/errors.rb +6 -1
- data/lib/cuprum/rails/map_errors.rb +29 -1
- data/lib/cuprum/rails/query.rb +1 -1
- data/lib/cuprum/rails/repository.rb +12 -25
- data/lib/cuprum/rails/request.rb +149 -60
- data/lib/cuprum/rails/resource.rb +119 -85
- data/lib/cuprum/rails/responders/base_responder.rb +78 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +9 -39
- data/lib/cuprum/rails/responders/html/rendering.rb +81 -0
- data/lib/cuprum/rails/responders/html/resource.rb +107 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +9 -38
- data/lib/cuprum/rails/responders/html.rb +2 -0
- data/lib/cuprum/rails/responders/html_responder.rb +8 -52
- data/lib/cuprum/rails/responders/json/resource.rb +3 -3
- data/lib/cuprum/rails/responders/json_responder.rb +31 -16
- data/lib/cuprum/rails/responders/matching.rb +29 -27
- data/lib/cuprum/rails/responders/serialization.rb +11 -9
- data/lib/cuprum/rails/responders.rb +1 -0
- data/lib/cuprum/rails/responses/head_response.rb +24 -0
- data/lib/cuprum/rails/responses/html/redirect_back_response.rb +55 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +19 -4
- data/lib/cuprum/rails/responses/html/render_response.rb +17 -5
- data/lib/cuprum/rails/responses/html.rb +6 -2
- data/lib/cuprum/rails/responses.rb +1 -0
- data/lib/cuprum/rails/result.rb +36 -0
- data/lib/cuprum/rails/routes.rb +36 -23
- data/lib/cuprum/rails/rspec/contract_helpers.rb +57 -0
- data/lib/cuprum/rails/rspec/contracts/action_contracts.rb +754 -0
- data/lib/cuprum/rails/rspec/contracts/actions/create_contracts.rb +289 -0
- data/lib/cuprum/rails/rspec/contracts/actions/destroy_contracts.rb +164 -0
- data/lib/cuprum/rails/rspec/contracts/actions/edit_contracts.rb +73 -0
- data/lib/cuprum/rails/rspec/contracts/actions/index_contracts.rb +108 -0
- data/lib/cuprum/rails/rspec/contracts/actions/new_contracts.rb +111 -0
- data/lib/cuprum/rails/rspec/contracts/actions/show_contracts.rb +72 -0
- data/lib/cuprum/rails/rspec/contracts/actions/update_contracts.rb +263 -0
- data/lib/cuprum/rails/rspec/contracts/actions.rb +8 -0
- data/lib/cuprum/rails/rspec/contracts/command_contracts.rb +479 -0
- data/lib/cuprum/rails/rspec/contracts/responder_contracts.rb +232 -0
- data/lib/cuprum/rails/rspec/contracts/routes_contracts.rb +363 -0
- data/lib/cuprum/rails/rspec/contracts/serializers_contracts.rb +70 -0
- data/lib/cuprum/rails/rspec/contracts.rb +8 -0
- data/lib/cuprum/rails/rspec/matchers/be_a_result_matcher.rb +64 -0
- data/lib/cuprum/rails/rspec/matchers.rb +41 -0
- data/lib/cuprum/rails/serializers/base_serializer.rb +60 -0
- data/lib/cuprum/rails/serializers/context.rb +84 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +95 -172
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +3 -3
- data/lib/cuprum/rails/serializers/json/properties_serializer.rb +252 -0
- data/lib/cuprum/rails/serializers/json.rb +2 -1
- data/lib/cuprum/rails/serializers.rb +3 -1
- data/lib/cuprum/rails/version.rb +1 -1
- data/lib/cuprum/rails.rb +19 -16
- metadata +73 -131
- data/lib/cuprum/rails/controller_action.rb +0 -121
- data/lib/cuprum/rails/errors/missing_parameters.rb +0 -33
- data/lib/cuprum/rails/errors/missing_primary_key.rb +0 -46
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +0 -34
- data/lib/cuprum/rails/rspec/command_contract.rb +0 -460
- data/lib/cuprum/rails/rspec/define_route_contract.rb +0 -84
- data/lib/cuprum/rails/serializers/json/serializer.rb +0 -66
@@ -0,0 +1,754 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/errors/not_found'
|
4
|
+
require 'cuprum/collections/repository'
|
5
|
+
require 'rspec/sleeping_king_studios/contract'
|
6
|
+
|
7
|
+
require 'cuprum/rails/map_errors'
|
8
|
+
require 'cuprum/rails/rspec/contract_helpers'
|
9
|
+
require 'cuprum/rails/rspec/contracts'
|
10
|
+
|
11
|
+
module Cuprum::Rails::RSpec::Contracts
|
12
|
+
# Namespace for RSpec action contracts, which validate action implementations.
|
13
|
+
module ActionContracts
|
14
|
+
# Contract validating the interface for an action.
|
15
|
+
module ShouldBeAnActionContract
|
16
|
+
extend RSpec::SleepingKingStudios::Contract
|
17
|
+
|
18
|
+
# @!method apply(example_group, **options)
|
19
|
+
# Adds the contract to the example group.
|
20
|
+
#
|
21
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
22
|
+
# which the contract is applied.
|
23
|
+
# @param options [Hash] additional options for the contract.
|
24
|
+
#
|
25
|
+
# @option options required_keywords [Array[Symbol]] additional keywords
|
26
|
+
# required by the #call method.
|
27
|
+
contract do |**options|
|
28
|
+
describe '.new' do
|
29
|
+
it { expect(described_class).to respond_to(:new).with(0).arguments }
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#call' do
|
33
|
+
let(:expected_keywords) do
|
34
|
+
%i[repository request] + options.fetch(:required_keywords, [])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should define the method' do
|
38
|
+
expect(action)
|
39
|
+
.to be_callable
|
40
|
+
.with(0).arguments
|
41
|
+
.and_keywords(*expected_keywords)
|
42
|
+
.and_any_keywords
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#options' do
|
47
|
+
include_examples 'should define reader', :options
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#params' do
|
51
|
+
include_examples 'should define reader', :params
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#repository' do
|
55
|
+
include_examples 'should define reader', :repository
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#request' do
|
59
|
+
include_examples 'should define reader', :request
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Contract validating the interface for a resourceful action.
|
65
|
+
module ShouldBeAResourceActionContract
|
66
|
+
extend RSpec::SleepingKingStudios::Contract
|
67
|
+
|
68
|
+
# @!method apply(example_group, **options)
|
69
|
+
# Adds the contract to the example group.
|
70
|
+
#
|
71
|
+
# @param example_group [RSpec::Core::ExampleGroup] the example group to
|
72
|
+
# which the contract is applied.
|
73
|
+
# @param options [Hash] additional options for the conrtact.
|
74
|
+
#
|
75
|
+
# @option options collection_class [String, Class] the expected class
|
76
|
+
# for the resource collection.
|
77
|
+
# @option options require_permitted_attributes [Boolean] if true, should
|
78
|
+
# require the resource to define permitted attributes as a non-empty
|
79
|
+
# Array.
|
80
|
+
# @option options required_keywords [Array[Symbol]] additional keywords
|
81
|
+
# required by the #call method.
|
82
|
+
|
83
|
+
contract do |**options|
|
84
|
+
include Cuprum::Rails::RSpec::Contracts::ActionContracts
|
85
|
+
|
86
|
+
let(:configured_params) do
|
87
|
+
return params if defined?(params)
|
88
|
+
|
89
|
+
{}
|
90
|
+
end
|
91
|
+
let(:configured_repository) do
|
92
|
+
return repository if defined?(repository)
|
93
|
+
|
94
|
+
Cuprum::Rails::Repository.new
|
95
|
+
end
|
96
|
+
let(:configured_request) do
|
97
|
+
return request if defined?(request)
|
98
|
+
|
99
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
100
|
+
end
|
101
|
+
let(:configured_resource) do
|
102
|
+
return resource if defined?(resource)
|
103
|
+
|
104
|
+
# :nocov:
|
105
|
+
Cuprum::Rails::Resource.new(entity_class: Book)
|
106
|
+
# :nocov:
|
107
|
+
end
|
108
|
+
let(:configured_action_options) do
|
109
|
+
return action_options if defined?(action_options)
|
110
|
+
|
111
|
+
{
|
112
|
+
repository: configured_repository,
|
113
|
+
resource: configured_resource
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
define_method(:call_action) do
|
118
|
+
action.call(request: configured_request, **configured_action_options)
|
119
|
+
end
|
120
|
+
|
121
|
+
include_contract 'should be an action',
|
122
|
+
required_keywords: [:resource, *options.fetch(:required_keywords, [])]
|
123
|
+
|
124
|
+
describe '#call' do
|
125
|
+
next unless options[:require_permitted_attributes]
|
126
|
+
|
127
|
+
describe 'with a permitted_attributes: nil' do
|
128
|
+
let(:resource) do
|
129
|
+
Cuprum::Rails::Resource.new(
|
130
|
+
name: 'books',
|
131
|
+
permitted_attributes: nil
|
132
|
+
)
|
133
|
+
end
|
134
|
+
let(:expected_error) do
|
135
|
+
Cuprum::Rails::Errors::ResourceError.new(
|
136
|
+
message: "permitted attributes can't be blank",
|
137
|
+
resource: configured_resource
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should return a failing result' do
|
142
|
+
expect(call_action)
|
143
|
+
.to be_a_failing_result
|
144
|
+
.with_error(expected_error)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'with a permitted_attributes: an empty Array' do
|
149
|
+
let(:resource) do
|
150
|
+
Cuprum::Rails::Resource.new(
|
151
|
+
name: 'books',
|
152
|
+
permitted_attributes: []
|
153
|
+
)
|
154
|
+
end
|
155
|
+
let(:expected_error) do
|
156
|
+
Cuprum::Rails::Errors::ResourceError.new(
|
157
|
+
message: "permitted attributes can't be blank",
|
158
|
+
resource: configured_resource
|
159
|
+
)
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should return a failing result' do
|
163
|
+
expect(call_action)
|
164
|
+
.to be_a_failing_result
|
165
|
+
.with_error(expected_error)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe '#collection' do
|
171
|
+
let(:expected_collection_class) do
|
172
|
+
next super() if defined?(super())
|
173
|
+
|
174
|
+
options
|
175
|
+
.fetch(:collection_class, Cuprum::Collections::Collection)
|
176
|
+
.then { |obj| obj.is_a?(String) ? obj.constantize : obj }
|
177
|
+
end
|
178
|
+
|
179
|
+
before(:example) { call_action }
|
180
|
+
|
181
|
+
include_examples 'should define reader', :collection
|
182
|
+
|
183
|
+
it { expect(action.collection).to be_a expected_collection_class }
|
184
|
+
|
185
|
+
it 'should set the collection name' do
|
186
|
+
expect(action.collection.name)
|
187
|
+
.to be == resource.name
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'should set the entity class' do
|
191
|
+
expect(action.collection.entity_class)
|
192
|
+
.to be == resource.entity_class
|
193
|
+
end
|
194
|
+
|
195
|
+
context 'when the repository defines a matching collection' do
|
196
|
+
let!(:existing_collection) do
|
197
|
+
configured_repository.find_or_create(
|
198
|
+
qualified_name: resource.qualified_name
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
it { expect(action.collection).to be existing_collection }
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'when there is a partially matching collection' do
|
206
|
+
let(:configured_repository) do
|
207
|
+
repository = super()
|
208
|
+
|
209
|
+
repository.find_or_create(
|
210
|
+
entity_class: resource.entity_class,
|
211
|
+
name: 'other_collection',
|
212
|
+
qualified_name: resource.qualified_name
|
213
|
+
)
|
214
|
+
|
215
|
+
repository
|
216
|
+
end
|
217
|
+
let!(:existing_collection) do
|
218
|
+
configured_repository[resource.qualified_name]
|
219
|
+
end
|
220
|
+
|
221
|
+
it { expect(action.collection).to be existing_collection }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#resource' do
|
226
|
+
include_examples 'should define reader', :resource
|
227
|
+
|
228
|
+
context 'when called with a resource' do
|
229
|
+
before(:example) { call_action }
|
230
|
+
|
231
|
+
it { expect(action.resource).to be == configured_resource }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe '#resource_id' do
|
236
|
+
include_examples 'should define reader', :resource_id
|
237
|
+
|
238
|
+
context 'when called with a resource' do
|
239
|
+
let(:params) { {} }
|
240
|
+
let(:request) { Cuprum::Rails::Request.new(params: params) }
|
241
|
+
|
242
|
+
before(:example) { call_action }
|
243
|
+
|
244
|
+
context 'when the parameters do not include a primary key' do
|
245
|
+
let(:params) { {} }
|
246
|
+
|
247
|
+
it { expect(action.resource_id).to be nil }
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'when the :id parameter is set' do
|
251
|
+
let(:primary_key_value) { 0 }
|
252
|
+
let(:params) { { 'id' => primary_key_value } }
|
253
|
+
|
254
|
+
it { expect(action.resource_id).to be primary_key_value }
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#resource_params' do
|
260
|
+
include_examples 'should define reader', :resource_params
|
261
|
+
|
262
|
+
context 'when called with a resource' do
|
263
|
+
let(:params) { {} }
|
264
|
+
let(:request) { Cuprum::Rails::Request.new(params: params) }
|
265
|
+
|
266
|
+
before(:example) { call_action }
|
267
|
+
|
268
|
+
context 'when the parameters do not include params for the ' \
|
269
|
+
'resource' \
|
270
|
+
do
|
271
|
+
let(:params) { {} }
|
272
|
+
|
273
|
+
it { expect(action.resource_params).to be == {} }
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'when the params for the resource are empty' do
|
277
|
+
let(:params) { { resource.singular_name => {} } }
|
278
|
+
|
279
|
+
it { expect(action.resource_params).to be == {} }
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'when the parameter for the resource is not a Hash' do
|
283
|
+
let(:params) { { resource.singular_name => 'invalid' } }
|
284
|
+
|
285
|
+
it { expect(action.resource_params).to be == 'invalid' }
|
286
|
+
end
|
287
|
+
|
288
|
+
context 'when the parameters include the params for resource' do
|
289
|
+
let(:params) do
|
290
|
+
resource_params =
|
291
|
+
configured_resource
|
292
|
+
.permitted_attributes
|
293
|
+
.then { |ary| ary || [] }
|
294
|
+
.to_h { |attr_name| [attr_name.to_s, "#{attr_name} value"] }
|
295
|
+
|
296
|
+
{
|
297
|
+
configured_resource.singular_name => resource_params
|
298
|
+
}
|
299
|
+
end
|
300
|
+
let(:expected) do
|
301
|
+
params[configured_resource.singular_name]
|
302
|
+
end
|
303
|
+
|
304
|
+
it { expect(action.resource_params).to be == expected }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
describe '#transaction' do
|
310
|
+
let(:transaction_class) { resource.entity_class }
|
311
|
+
|
312
|
+
before(:example) { call_action }
|
313
|
+
|
314
|
+
it 'should define the private method' do
|
315
|
+
expect(action).to respond_to(:transaction, true).with(0).arguments
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'should yield the block' do
|
319
|
+
expect { |block| action.send(:transaction, &block) }
|
320
|
+
.to yield_control
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'should wrap the block in a transaction' do
|
324
|
+
in_transaction = false
|
325
|
+
|
326
|
+
allow(transaction_class).to receive(:transaction) do |&block|
|
327
|
+
in_transaction = true
|
328
|
+
|
329
|
+
block.call
|
330
|
+
|
331
|
+
in_transaction = false
|
332
|
+
end
|
333
|
+
|
334
|
+
action.send(:transaction) do
|
335
|
+
expect(in_transaction).to be true
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context 'when the block contains a failing step' do
|
340
|
+
let(:expected_error) do
|
341
|
+
Cuprum::Error.new(message: 'Something went wrong.')
|
342
|
+
end
|
343
|
+
|
344
|
+
before(:example) do
|
345
|
+
action.define_singleton_method(:failing_step) do
|
346
|
+
error = Cuprum::Error.new(message: 'Something went wrong.')
|
347
|
+
|
348
|
+
step { failure(error) }
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'should return the failing result' do
|
353
|
+
expect(action.send(:transaction) { action.failing_step })
|
354
|
+
.to be_a_failing_result
|
355
|
+
.with_error(expected_error)
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'should roll back the transaction' do
|
359
|
+
rollback = false
|
360
|
+
|
361
|
+
allow(transaction_class).to receive(:transaction) do |&block|
|
362
|
+
block.call
|
363
|
+
rescue ActiveRecord::Rollback
|
364
|
+
rollback = true
|
365
|
+
end
|
366
|
+
|
367
|
+
action.send(:transaction) { action.failing_step }
|
368
|
+
|
369
|
+
expect(rollback).to be true
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Contract asserting the action finds and returns the requested entity.
|
377
|
+
module ShouldFindTheEntityContract
|
378
|
+
extend RSpec::SleepingKingStudios::Contract
|
379
|
+
|
380
|
+
# @method apply(example_group, existing_entity:, **options, &block)
|
381
|
+
# Adds the contract to the example group.
|
382
|
+
#
|
383
|
+
# @param example_group [RSpec::Core::ExampleGroup] The example group to
|
384
|
+
# which the contract is applied.
|
385
|
+
# @param existing_entity [Object] The existing entity to destroy.
|
386
|
+
#
|
387
|
+
# @option options [Hash<String>] expected_value The expected value for
|
388
|
+
# the passing result. Defaults to a Hash with the destroyed entity.
|
389
|
+
# @option options [Hash<String>] params The parameters used to build the
|
390
|
+
# request. Defaults to the id of the entity.
|
391
|
+
#
|
392
|
+
# @yield Additional configuration or examples.
|
393
|
+
|
394
|
+
contract do |existing_entity:, **options, &block|
|
395
|
+
describe '#call' do
|
396
|
+
include Cuprum::Rails::RSpec::ContractHelpers
|
397
|
+
|
398
|
+
context 'when the entity exists' do
|
399
|
+
let(:request) do
|
400
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
401
|
+
end
|
402
|
+
let(:configured_existing_entity) do
|
403
|
+
option_with_default(existing_entity)
|
404
|
+
end
|
405
|
+
let(:configured_params) do
|
406
|
+
resource_id =
|
407
|
+
configured_existing_entity[configured_resource.primary_key]
|
408
|
+
|
409
|
+
option_with_default(
|
410
|
+
options[:params],
|
411
|
+
default: { 'id' => resource_id }
|
412
|
+
)
|
413
|
+
end
|
414
|
+
let(:configured_expected_value) do
|
415
|
+
resource_name = configured_resource.singular_name
|
416
|
+
|
417
|
+
option_with_default(
|
418
|
+
options[:expected_value],
|
419
|
+
default: { resource_name => configured_existing_entity }
|
420
|
+
)
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'should return a passing result' do
|
424
|
+
expect(call_action)
|
425
|
+
.to be_a_passing_result
|
426
|
+
.with_value(configured_expected_value)
|
427
|
+
end
|
428
|
+
|
429
|
+
instance_exec(&block) if block
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Contract asserting the action requires a valid entity.
|
436
|
+
module ShouldRequireExistingEntityContract
|
437
|
+
extend RSpec::SleepingKingStudios::Contract
|
438
|
+
|
439
|
+
# @!method apply(example_group, **options)
|
440
|
+
# Adds the contract to the example group.
|
441
|
+
#
|
442
|
+
# @param example_group [RSpec::Core::ExampleGroup] The example group to
|
443
|
+
# which the contract is applied.
|
444
|
+
#
|
445
|
+
# @option options [Hash<String>] params The parameters used to build the
|
446
|
+
# request. Defaults to the id of the entity.
|
447
|
+
# @option options [Object] primary_key_value The value of the primary
|
448
|
+
# key for the missing entity.
|
449
|
+
#
|
450
|
+
# @yield Additional configuration or examples.
|
451
|
+
|
452
|
+
contract do |**options, &block|
|
453
|
+
describe '#call' do
|
454
|
+
include Cuprum::Rails::RSpec::ContractHelpers
|
455
|
+
|
456
|
+
context 'when the entity does not exist' do
|
457
|
+
let(:request) do
|
458
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
459
|
+
end
|
460
|
+
let(:configured_primary_key_value) do
|
461
|
+
option_with_default(
|
462
|
+
options[:primary_key_value],
|
463
|
+
default: 0
|
464
|
+
)
|
465
|
+
end
|
466
|
+
let(:configured_params) do
|
467
|
+
option_with_default(
|
468
|
+
options[:params],
|
469
|
+
default: {}
|
470
|
+
)
|
471
|
+
.merge({ 'id' => configured_primary_key_value })
|
472
|
+
end
|
473
|
+
let(:expected_error) do
|
474
|
+
Cuprum::Collections::Errors::NotFound.new(
|
475
|
+
attribute_name: configured_resource.primary_key.to_s,
|
476
|
+
attribute_value: configured_primary_key_value,
|
477
|
+
collection_name: configured_resource.name,
|
478
|
+
primary_key: true
|
479
|
+
)
|
480
|
+
end
|
481
|
+
|
482
|
+
before(:example) do
|
483
|
+
primary_key_name = configured_resource.primary_key
|
484
|
+
|
485
|
+
resource
|
486
|
+
.entity_class
|
487
|
+
.where(primary_key_name => configured_primary_key_value)
|
488
|
+
.destroy_all
|
489
|
+
end
|
490
|
+
|
491
|
+
it 'should return a failing result' do
|
492
|
+
expect(call_action)
|
493
|
+
.to be_a_failing_result
|
494
|
+
.with_error(expected_error)
|
495
|
+
end
|
496
|
+
|
497
|
+
instance_exec(&block) if block
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
# Contract asserting the action requires resource parameters.
|
504
|
+
module ShouldRequireParametersContract
|
505
|
+
extend RSpec::SleepingKingStudios::Contract
|
506
|
+
|
507
|
+
# @!method apply(example_group)
|
508
|
+
# Adds the contract to the example group.
|
509
|
+
#
|
510
|
+
# @param example_group [RSpec::Core::ExampleGroup] The example group to
|
511
|
+
# which the contract is applied.
|
512
|
+
#
|
513
|
+
# @option options [Hash<String>] params The parameters used to build the
|
514
|
+
# request. Defaults to an empty Hash.
|
515
|
+
#
|
516
|
+
# @yield Additional configuration or examples.
|
517
|
+
|
518
|
+
contract do |**options, &block|
|
519
|
+
describe '#call' do
|
520
|
+
include Cuprum::Rails::RSpec::ContractHelpers
|
521
|
+
|
522
|
+
context 'when the parameters do not include params for the resource' \
|
523
|
+
do
|
524
|
+
let(:request) do
|
525
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
526
|
+
end
|
527
|
+
let(:configured_params) do
|
528
|
+
option_with_default(options[:params], default: {})
|
529
|
+
.dup
|
530
|
+
.tap do |hsh|
|
531
|
+
hsh.delete(configured_resource.singular_name)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
let(:configured_expected_error) do
|
535
|
+
errors = Stannum::Errors.new.tap do |err|
|
536
|
+
err[configured_resource.singular_name]
|
537
|
+
.add(Stannum::Constraints::Presence::TYPE)
|
538
|
+
end
|
539
|
+
|
540
|
+
Cuprum::Rails::Errors::InvalidParameters.new(errors: errors)
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'should return a failing result' do
|
544
|
+
expect(call_action)
|
545
|
+
.to be_a_failing_result
|
546
|
+
.with_error(configured_expected_error)
|
547
|
+
end
|
548
|
+
|
549
|
+
instance_exec(&block) if block
|
550
|
+
end
|
551
|
+
|
552
|
+
context 'when the resource parameters are not a Hash' do
|
553
|
+
let(:request) do
|
554
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
555
|
+
end
|
556
|
+
let(:configured_params) do
|
557
|
+
option_with_default(options[:params], default: {})
|
558
|
+
.merge(configured_resource.singular_name => 'invalid')
|
559
|
+
end
|
560
|
+
let(:configured_expected_error) do
|
561
|
+
errors = Stannum::Errors.new.tap do |err|
|
562
|
+
err[configured_resource.singular_name].add(
|
563
|
+
Stannum::Constraints::Type::TYPE,
|
564
|
+
allow_empty: true,
|
565
|
+
required: true,
|
566
|
+
type: Hash
|
567
|
+
)
|
568
|
+
end
|
569
|
+
|
570
|
+
Cuprum::Rails::Errors::InvalidParameters.new(errors: errors)
|
571
|
+
end
|
572
|
+
|
573
|
+
it 'should return a failing result' do
|
574
|
+
expect(call_action)
|
575
|
+
.to be_a_failing_result
|
576
|
+
.with_error(configured_expected_error)
|
577
|
+
end
|
578
|
+
|
579
|
+
instance_exec(&block) if block
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
# Contract asserting the action requires a primary key.
|
586
|
+
module ShouldRequirePrimaryKeyContract
|
587
|
+
extend RSpec::SleepingKingStudios::Contract
|
588
|
+
|
589
|
+
# @!method apply(example_group, **options, &block)
|
590
|
+
# Adds the contract to the example group.
|
591
|
+
#
|
592
|
+
# @param example_group [RSpec::Core::ExampleGroup] The example group to
|
593
|
+
# which the contract is applied.
|
594
|
+
#
|
595
|
+
# @option options [Hash<String>] params The parameters used to build the
|
596
|
+
# request. Defaults to an empty Hash.
|
597
|
+
#
|
598
|
+
# @yield Additional configuration or examples.
|
599
|
+
|
600
|
+
contract do |**options, &block|
|
601
|
+
describe '#call' do
|
602
|
+
include Cuprum::Rails::RSpec::ContractHelpers
|
603
|
+
|
604
|
+
context 'when the parameters do not include a primary key' do
|
605
|
+
let(:request) do
|
606
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
607
|
+
end
|
608
|
+
let(:configured_params) do
|
609
|
+
option_with_default(options[:params], default: {})
|
610
|
+
.dup
|
611
|
+
.tap { |hsh| hsh.delete('id') }
|
612
|
+
end
|
613
|
+
let(:configured_expected_error) do
|
614
|
+
errors = Stannum::Errors.new.tap do |err|
|
615
|
+
err['id'].add(Stannum::Constraints::Presence::TYPE)
|
616
|
+
end
|
617
|
+
|
618
|
+
Cuprum::Rails::Errors::InvalidParameters.new(errors: errors)
|
619
|
+
end
|
620
|
+
|
621
|
+
it 'should return a failing result' do
|
622
|
+
expect(call_action)
|
623
|
+
.to be_a_failing_result
|
624
|
+
.with_error(configured_expected_error)
|
625
|
+
end
|
626
|
+
|
627
|
+
instance_exec(&block) if block
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
# Contract asserting the action validates the created or updated entity.
|
634
|
+
module ShouldValidateAttributesContract
|
635
|
+
extend RSpec::SleepingKingStudios::Contract
|
636
|
+
|
637
|
+
# @!method apply(example_group, invalid_attributes:, expected_attributes: nil, **options)
|
638
|
+
# Adds the contract to the example group.
|
639
|
+
#
|
640
|
+
# @param example_group [RSpec::Core::ExampleGroup] The example group to
|
641
|
+
# which the contract is applied.
|
642
|
+
# @param invalid_attributes [Hash<String>] A set of attributes that will
|
643
|
+
# fail validation.
|
644
|
+
#
|
645
|
+
# @option options [Object] existing_entity The existing entity, if any.
|
646
|
+
# @option options [Hash<String>] expected_attributes The expected
|
647
|
+
# attributes for the returned object. Defaults to the value of
|
648
|
+
# invalid_attributes.
|
649
|
+
# @option options [Hash<String>] params The parameters used to build the
|
650
|
+
# request. Defaults to the given attributes.
|
651
|
+
#
|
652
|
+
# @yield Additional configuration or examples.
|
653
|
+
|
654
|
+
contract do |invalid_attributes:, **options, &block|
|
655
|
+
describe '#call' do
|
656
|
+
include Cuprum::Rails::RSpec::ContractHelpers
|
657
|
+
|
658
|
+
context 'when the resource params fail validation' do
|
659
|
+
let(:request) do
|
660
|
+
Cuprum::Rails::Request.new(params: configured_params)
|
661
|
+
end
|
662
|
+
let(:configured_invalid_attributes) do
|
663
|
+
option_with_default(invalid_attributes)
|
664
|
+
end
|
665
|
+
let(:configured_params) do
|
666
|
+
resource_name = configured_resource.singular_name
|
667
|
+
|
668
|
+
option_with_default(
|
669
|
+
options[:params],
|
670
|
+
default: {}
|
671
|
+
).merge({ resource_name => configured_invalid_attributes })
|
672
|
+
end
|
673
|
+
let(:configured_existing_entity) do
|
674
|
+
option_with_default(options[:existing_entity])
|
675
|
+
end
|
676
|
+
let(:configured_expected_attributes) do
|
677
|
+
option_with_default(
|
678
|
+
options[:expected_attributes],
|
679
|
+
default: (configured_existing_entity&.attributes || {}).merge(
|
680
|
+
configured_invalid_attributes
|
681
|
+
)
|
682
|
+
)
|
683
|
+
end
|
684
|
+
let(:configured_expected_entity) do
|
685
|
+
if configured_existing_entity
|
686
|
+
repository
|
687
|
+
.find_or_create(
|
688
|
+
qualified_name: resource.qualified_name
|
689
|
+
)
|
690
|
+
.assign_one
|
691
|
+
.call(
|
692
|
+
attributes: configured_invalid_attributes,
|
693
|
+
entity: configured_existing_entity.clone
|
694
|
+
)
|
695
|
+
.value
|
696
|
+
.tap(&:valid?)
|
697
|
+
else
|
698
|
+
action
|
699
|
+
.resource
|
700
|
+
.entity_class
|
701
|
+
.new(configured_expected_attributes)
|
702
|
+
.tap(&:valid?)
|
703
|
+
end
|
704
|
+
end
|
705
|
+
let(:configured_expected_value) do
|
706
|
+
matcher =
|
707
|
+
be_a(configured_expected_entity.class)
|
708
|
+
.and(have_attributes(configured_expected_entity.attributes))
|
709
|
+
option_with_default(
|
710
|
+
options[:expected_value],
|
711
|
+
default: {
|
712
|
+
configured_resource.singular_name => matcher
|
713
|
+
}
|
714
|
+
)
|
715
|
+
end
|
716
|
+
let(:configured_expected_error) do
|
717
|
+
errors =
|
718
|
+
Cuprum::Rails::MapErrors
|
719
|
+
.instance
|
720
|
+
.call(native_errors: configured_expected_entity.errors)
|
721
|
+
|
722
|
+
Cuprum::Collections::Errors::FailedValidation.new(
|
723
|
+
entity_class: configured_resource.entity_class,
|
724
|
+
errors: scope_validation_errors(errors)
|
725
|
+
)
|
726
|
+
end
|
727
|
+
|
728
|
+
def scope_validation_errors(errors)
|
729
|
+
mapped_errors = Stannum::Errors.new
|
730
|
+
resource_name = configured_resource.singular_name
|
731
|
+
|
732
|
+
errors.each do |err|
|
733
|
+
mapped_errors
|
734
|
+
.dig(resource_name, *err[:path].map(&:to_s))
|
735
|
+
.add(err[:type], message: err[:message], **err[:data])
|
736
|
+
end
|
737
|
+
|
738
|
+
mapped_errors
|
739
|
+
end
|
740
|
+
|
741
|
+
it 'should return a failing result' do
|
742
|
+
expect(call_action)
|
743
|
+
.to be_a_failing_result
|
744
|
+
.with_value(deep_match(configured_expected_value))
|
745
|
+
.and_error(configured_expected_error)
|
746
|
+
end
|
747
|
+
|
748
|
+
instance_exec(&block) if block
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
end
|
754
|
+
end
|