much-rails 0.1.3 → 0.2.4

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