much-rails 0.1.3 → 0.2.4

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.
@@ -34,7 +34,7 @@ module MuchRails::Records::AlwaysDestroyable
34
34
 
35
35
  assert_that(subject.destroy).is_true
36
36
 
37
- # won't raise MuchRails::Records::ValidateDestroy::DestructionInvalid
37
+ # won't raise MuchRails::Records::DestructionInvalid
38
38
  subject.destroy!
39
39
 
40
40
  assert_that(subject.destroyable?).is_true
@@ -32,7 +32,7 @@ module MuchRails::Records::NotDestroyable
32
32
  assert_that(subject.destroy).is_false
33
33
 
34
34
  assert_that(->{ subject.destroy! })
35
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
35
+ .raises(MuchRails::Records::DestructionInvalid)
36
36
 
37
37
  assert_that(subject.destroyable?).is_false
38
38
  end
@@ -62,17 +62,17 @@ module MuchRails::Records::ValidateDestroy
62
62
 
63
63
  exception =
64
64
  assert_that(->{ subject.destroy! })
65
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
65
+ .raises(MuchRails::Records::DestructionInvalid)
66
66
  assert_that(exception.message)
67
67
  .equals("TEST DESTRUCTION ERROR1\nTEST DESTRUCTION ERROR2")
68
- assert_that(exception.destruction_errors)
68
+ assert_that(exception.errors)
69
69
  .equals(base: subject.destruction_error_messages.to_a)
70
70
  assert_that(subject.super_destroy_called).is_true
71
71
 
72
72
  exception =
73
73
  assert_that(->{ subject.destroy!(as: :thing) })
74
- .raises(MuchRails::Records::ValidateDestroy::DestructionInvalid)
75
- assert_that(exception.destruction_errors)
74
+ .raises(MuchRails::Records::DestructionInvalid)
75
+ assert_that(exception.errors)
76
76
  .equals(thing: subject.destruction_error_messages.to_a)
77
77
 
78
78
  subject.super_destroy_called = nil
@@ -138,9 +138,85 @@ module MuchRails::Records::ValidateDestroy
138
138
  end
139
139
  end
140
140
 
141
+ class DestructionInvalidTests < UnitTests
142
+ desc "MuchRails::Records::DestructionInvalid"
143
+ subject{ exception_class }
144
+
145
+ let(:exception_class){ MuchRails::Records::DestructionInvalid }
146
+
147
+ let(:record){ FakeRecordClass.new }
148
+
149
+ should "be configured as expected" do
150
+ assert_that(subject < StandardError).is_true
151
+ end
152
+ end
153
+
154
+ class DestructionInvalidInitTests < DestructionInvalidTests
155
+ desc "when init"
156
+ subject{ exception_class.new(record) }
157
+
158
+ should have_readers :record, :errors, :error_full_messages
159
+
160
+ should "know its attributes when destruction errors exist" do
161
+ record.destruction_errors_exist = true
162
+ record.validate_destroy
163
+
164
+ assert_that(subject.message)
165
+ .equals(record.destruction_error_messages.to_a.join("\n"))
166
+ assert_that(subject.record).is(record)
167
+ assert_that(subject.errors).equals(
168
+ base: record.destruction_error_messages.to_a,
169
+ )
170
+ assert_that(subject.error_full_messages)
171
+ .equals(record.destruction_error_messages.to_a)
172
+ end
173
+
174
+ should "know its attributes when destruction errors don't exist" do
175
+ assert_that(subject.message).equals("")
176
+ assert_that(subject.record).is(record)
177
+ assert_that(subject.errors).equals({})
178
+ assert_that(subject.error_full_messages).equals([])
179
+ end
180
+ end
181
+
182
+ class DestructionInvalidInitWithFieldNameTests < DestructionInvalidTests
183
+ desc "when init with a field name"
184
+ subject{ exception_class.new(record, field_name: field_name) }
185
+
186
+ let(:field_name){ Factory.string }
187
+
188
+ should "know its attributes when destruction errors exist" do
189
+ record.destruction_errors_exist = true
190
+ record.validate_destroy
191
+
192
+ assert_that(subject.message)
193
+ .equals(record.destruction_error_messages.to_a.join("\n"))
194
+ assert_that(subject.record).is(record)
195
+ assert_that(subject.errors).equals(
196
+ field_name.to_sym => record.destruction_error_messages.to_a,
197
+ )
198
+ assert_that(subject.error_full_messages)
199
+ .equals(
200
+ record
201
+ .destruction_error_messages
202
+ .map do |m|
203
+ ActiveModel::Error.new(record, field_name, m).full_message
204
+ end,
205
+ )
206
+ end
207
+
208
+ should "know its attributes when destruction errors don't exist" do
209
+ assert_that(subject.message).equals("")
210
+ assert_that(subject.record).is(record)
211
+ assert_that(subject.errors).equals({})
212
+ assert_that(subject.error_full_messages).equals([])
213
+ end
214
+ end
215
+
141
216
  require "active_record"
142
217
 
143
218
  class FakeRecordBaseClass
219
+ extend ActiveRecord::Translation
144
220
  # Include ActiveRecord::Persistence to test the `destroy!` logic
145
221
  # (the `_raise_record_not_destroyed` method) that we had to re-implement in
146
222
  # the MuchRails::Records::ValidateDestroy.
@@ -150,6 +226,10 @@ module MuchRails::Records::ValidateDestroy
150
226
 
151
227
  attr_accessor :destruction_errors_exist
152
228
 
229
+ def self.base_class?
230
+ true
231
+ end
232
+
153
233
  def destroy
154
234
  @super_destroy_called = true
155
235
  end
@@ -44,49 +44,209 @@ module MuchRails::SaveService
44
44
  end
45
45
  end
46
46
 
47
- class RecordInvalidErrorSetupTests < ReceiverTests
48
- desc "with an ActiveRecord::RecordInvalid error"
47
+ class ReceiverInitTests < ReceiverTests
48
+ desc "when init"
49
+ subject{ receiver_class.new(exception: exception) }
50
+
51
+ let(:exception){ nil }
52
+ end
53
+
54
+ class ReceiverInitAroundCallCallbackTests < ReceiverInitTests
55
+ desc "around_call callback"
49
56
  setup do
50
- Assert.stub(exception, :record){ record }
57
+ Assert.stub(
58
+ MuchRails::SaveService::ValidationErrors,
59
+ :exception_classes,
60
+ ){ exception_classes }
61
+ Assert.stub_on_call(
62
+ MuchRails::SaveService::ValidationErrors,
63
+ :result_for,
64
+ ) do |call|
65
+ @result_for_call = call
66
+ validation_error_result
67
+ end
51
68
  end
52
69
 
53
- let(:exception){ ActiveRecord::RecordInvalid.new }
70
+ let(:exception){ exceptions.sample }
71
+ let(:exceptions) do
72
+ [
73
+ RuntimeError.new(Factory.string),
74
+ ArgumentError.new(Factory.string),
75
+ ActiveRecord::RecordInvalid.new(FakeRecord.new),
76
+ ]
77
+ end
78
+ let(:exception_classes){ exceptions.map(&:class) }
79
+ let(:validation_error_result) do
80
+ MuchResult.failure(error: result_error_message)
81
+ end
82
+ let(:result_error_message){ Factory.string }
83
+
84
+ should "rescue raised exceptions and "\
85
+ "use the ValidationErrors to build a result" do
86
+ result = subject.call
87
+
88
+ assert_that(result.failure?).is_true
89
+ assert_that(result.error).equals(result_error_message)
90
+ end
54
91
  end
55
92
 
56
- class ExceptionWithRecordErrorsTests < RecordInvalidErrorSetupTests
57
- desc "with an exception that has record errors"
93
+ class ValidationErrorsTests < UnitTests
94
+ desc "ValidationErrors"
95
+ subject{ unit_class::ValidationErrors }
58
96
 
59
- let(:record){ @fake_record ||= FakeRecord.new }
97
+ should have_imeths :add, :exception_classes, :result_for
98
+ should have_imeths :service_validation_errors
99
+
100
+ should "know its ServiceValidationErrors" do
101
+ assert_that(subject.service_validation_errors)
102
+ .is_an_instance_of(MuchRails::ServiceValidationErrors)
103
+ assert_that(subject.service_validation_errors.exception_classes)
104
+ .includes(ActiveRecord::RecordInvalid)
105
+ end
106
+ end
60
107
 
61
- should "return a failure result with the exception and record errors" do
62
- result = subject.call(exception: exception)
108
+ class ValidationErrorsAddTests < ValidationErrorsTests
109
+ desc ".add"
63
110
 
64
- assert_that(result.failure?).is_true
65
- assert_that(result.exception).equals(exception)
66
- assert_that(result.validation_errors)
67
- .equals(some_field: %w[ERROR1 ERROR2])
68
- assert_that(result.validation_error_messages)
69
- .equals(["some_field ERROR1", "some_field ERROR2"])
111
+ setup do
112
+ Assert.stub_on_call(subject.service_validation_errors, :add) do |call|
113
+ @add_call = call
114
+ end
115
+ end
116
+
117
+ let(:exception_class){ StandardError }
118
+ let(:block){ proc{ MuchResult.failure } }
119
+
120
+ should "call #add on its ServiceValidationErrors" do
121
+ subject.add(exception_class, &block)
122
+ assert_that(@add_call.args).equals([exception_class])
123
+ assert_that(@add_call.block).is(block)
124
+ end
125
+ end
126
+
127
+ class ValidationErrorsExceptionClassesTests < ValidationErrorsTests
128
+ desc ".exception_classes"
129
+
130
+ setup do
131
+ Assert.stub(
132
+ subject.service_validation_errors,
133
+ :exception_classes,
134
+ ){ exception_classes }
135
+ end
136
+
137
+ let(:exception_classes) do
138
+ [
139
+ StandardError,
140
+ ArgumentError,
141
+ ]
142
+ end
143
+
144
+ should "call #exception_classes on its ServiceValidationErrors" do
145
+ assert_that(subject.exception_classes).is(exception_classes)
146
+ end
147
+ end
148
+
149
+ class ValidationErrorsResultForTests < ValidationErrorsTests
150
+ desc ".result_for"
151
+
152
+ setup do
153
+ Assert.stub_on_call(
154
+ subject.service_validation_errors,
155
+ :result_for,
156
+ ) do |call|
157
+ @result_for_call = call
158
+ result_for_result
159
+ end
160
+ end
161
+
162
+ let(:exception){ StandardError.new(Factory.string) }
163
+ let(:result_for_result){ MuchResult.failure }
164
+
165
+ should "call #result_for on its ServiceValidationErrors" do
166
+ assert_that(subject.result_for(exception)).is(result_for_result)
167
+ assert_that(@result_for_call.args).equals([exception])
70
168
  end
71
169
  end
72
170
 
73
- class ExceptionWithoutRecordErrorsTests < RecordInvalidErrorSetupTests
74
- desc "with an exception that has no record errors"
171
+ class ValidationErrorsResultForRecordInvalidTests < ValidationErrorsTests
172
+ desc "when .result_for is passed an ActiveRecord::RecordInvalid"
173
+
174
+ let(:exception){ ActiveRecord::RecordInvalid.new(record) }
175
+ let(:record){ FakeRecord.new }
75
176
 
76
- let(:record){ nil }
177
+ let(:no_record_exception){ ActiveRecord::RecordInvalid.new }
178
+
179
+ should "return a failure result with the record and validation errors" do
180
+ result = subject.result_for(exception)
181
+
182
+ assert_that(result.failure?).is_true
183
+ assert_that(result.exception).equals(exception)
184
+ assert_that(result.validation_errors).equals(record.errors.to_h)
185
+ assert_that(result.validation_error_messages)
186
+ .equals(record.errors.full_messages.to_a)
187
+ end
77
188
 
78
189
  should "return a failure result with the exception and empty "\
79
190
  "record errors" do
80
- result = subject.call(exception: exception)
191
+ result = subject.result_for(no_record_exception)
81
192
 
82
193
  assert_that(result.failure?).is_true
83
- assert_that(result.exception).equals(exception)
194
+ assert_that(result.exception).equals(no_record_exception)
84
195
  assert_that(result.validation_errors).equals({})
85
196
  assert_that(result.validation_error_messages).equals([])
86
197
  end
87
198
  end
88
199
 
200
+ class FailureResultTests < UnitTests
201
+ desc "FailureResult"
202
+ subject{ unit_class::FailureResult }
203
+
204
+ setup do
205
+ Assert.stub_tap_on_call(MuchResult, :failure) do |_, call|
206
+ @much_result_failure_call = call
207
+ end
208
+ end
209
+
210
+ let(:exception){ StandardError.new(Factory.string) }
211
+ let(:validation_errors){ { Factory.symbol => Factory.string } }
212
+ let(:custom_value){ Factory.string }
213
+
214
+ should have_imeths :new
215
+
216
+ should "use MuchResult.failure to build a result" do
217
+ result =
218
+ subject.new(
219
+ exception: exception,
220
+ validation_errors: validation_errors,
221
+ custom: custom_value,
222
+ )
223
+ assert_that(result.failure?).is_true
224
+ assert_that(@much_result_failure_call.kargs)
225
+ .equals(
226
+ exception: exception,
227
+ validation_errors: validation_errors,
228
+ custom: custom_value,
229
+ )
230
+ end
231
+
232
+ should "raise an error without an exception or validation errors" do
233
+ assert_that{
234
+ subject.new(validation_errors: validation_errors)
235
+ }.raises(ArgumentError)
236
+ end
237
+
238
+ should "raise an error without an exception or validation errors" do
239
+ assert_that{
240
+ subject.new(exception: exception)
241
+ }.raises(ArgumentError)
242
+ end
243
+ end
244
+
89
245
  class FakeRecord
246
+ def self.i18n_scope
247
+ "fake_record"
248
+ end
249
+
90
250
  def errors
91
251
  Errors.new
92
252
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MuchRails::ServiceValidationErrors
4
+ class UnitTests < Assert::Context
5
+ desc "MuchRails::ServiceValidationErrors"
6
+ subject{ unit_class }
7
+
8
+ let(:unit_class){ MuchRails::ServiceValidationErrors }
9
+ end
10
+
11
+ class InitTests < UnitTests
12
+ desc "when init"
13
+ subject{ unit_class.new }
14
+
15
+ should have_readers :hash
16
+ should have_imeths :add, :exception_classes, :result_for
17
+ end
18
+
19
+ class InitAddTests < InitTests
20
+ desc "#add"
21
+
22
+ let(:exception_class){ StandardError }
23
+ let(:block){ proc{ MuchResult.failure } }
24
+
25
+ let(:invalid_exception_class) do
26
+ [
27
+ Class.new,
28
+ Factory.string,
29
+ nil,
30
+ ].sample
31
+ end
32
+
33
+ should "add an exception class and block" do
34
+ subject.add(exception_class, &block)
35
+ assert_that(subject.hash[exception_class]).is(block)
36
+ end
37
+
38
+ should "raise an error when it's not passed an Exception" do
39
+ assert_that{
40
+ subject.add(invalid_exception_class, &block)
41
+ }.raises(ArgumentError)
42
+ end
43
+ end
44
+
45
+ class InitExceptionClassesTests < InitTests
46
+ desc "#exception_classes"
47
+
48
+ setup do
49
+ exception_classes.each do |exception_class|
50
+ subject.add(exception_class, &block)
51
+ end
52
+ end
53
+
54
+ let(:exception_classes) do
55
+ [
56
+ StandardError,
57
+ ArgumentError,
58
+ RuntimeError,
59
+ ]
60
+ end
61
+ let(:block){ proc{ MuchResult.failure } }
62
+
63
+ should "return all the added exception classes" do
64
+ assert_that(subject.exception_classes).equals(exception_classes)
65
+ end
66
+ end
67
+
68
+ class InitResultForTests < InitTests
69
+ desc "#result_for"
70
+
71
+ setup do
72
+ subject.add(exception_class, &block)
73
+ end
74
+
75
+ let(:exception){ exception_class.new(Factory.string) }
76
+ let(:exception_class){ StandardError }
77
+ let(:block) do
78
+ proc{ MuchResult.failure(error_message: failure_result_error_message) }
79
+ end
80
+ let(:failure_result_error_message){ Factory.string }
81
+
82
+ let(:inherited_exception){ RuntimeError.new(Factory.string) }
83
+
84
+ let(:invalid_exception){ Exception.new(Factory.string) }
85
+
86
+ should "return the result of calling the added block "\
87
+ "for the exception class" do
88
+ result = subject.result_for(exception)
89
+ assert_that(result.failure?).is_true
90
+ assert_that(result.error_message).equals(failure_result_error_message)
91
+ end
92
+
93
+ should "return the result of calling the added block "\
94
+ "for an exception class ancestor" do
95
+ result = subject.result_for(exception)
96
+ assert_that(result.failure?).is_true
97
+ assert_that(result.error_message).equals(failure_result_error_message)
98
+ end
99
+
100
+ should "raise an error if a block hasn't been added "\
101
+ "for the exception class" do
102
+ assert_that{
103
+ subject.result_for(invalid_exception)
104
+ }.raises(ArgumentError)
105
+ end
106
+ end
107
+ end