active_storage_validations 1.0.4 → 3.0.2

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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +785 -245
  3. data/config/locales/da.yml +63 -0
  4. data/config/locales/de.yml +60 -19
  5. data/config/locales/en-GB.yml +63 -0
  6. data/config/locales/en.yml +60 -20
  7. data/config/locales/es.yml +60 -19
  8. data/config/locales/fr.yml +60 -19
  9. data/config/locales/it.yml +60 -19
  10. data/config/locales/ja.yml +60 -19
  11. data/config/locales/nl.yml +60 -19
  12. data/config/locales/pl.yml +60 -19
  13. data/config/locales/pt-BR.yml +60 -19
  14. data/config/locales/ru.yml +60 -19
  15. data/config/locales/sv.yml +63 -0
  16. data/config/locales/tr.yml +60 -19
  17. data/config/locales/uk.yml +60 -19
  18. data/config/locales/vi.yml +60 -19
  19. data/config/locales/zh-CN.yml +60 -19
  20. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
  21. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
  22. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +46 -0
  23. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +56 -0
  24. data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
  25. data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
  26. data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +89 -0
  27. data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
  28. data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
  29. data/lib/active_storage_validations/analyzer.rb +88 -0
  30. data/lib/active_storage_validations/aspect_ratio_validator.rb +157 -97
  31. data/lib/active_storage_validations/attached_validator.rb +22 -5
  32. data/lib/active_storage_validations/base_comparison_validator.rb +83 -0
  33. data/lib/active_storage_validations/content_type_validator.rb +219 -31
  34. data/lib/active_storage_validations/dimension_validator.rb +187 -97
  35. data/lib/active_storage_validations/duration_validator.rb +70 -0
  36. data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +56 -0
  37. data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
  38. data/lib/active_storage_validations/limit_validator.rb +76 -9
  39. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
  40. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
  41. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +150 -0
  42. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +98 -39
  43. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
  44. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
  45. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
  46. data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +39 -0
  47. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
  48. data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
  49. data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
  50. data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
  51. data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +57 -0
  52. data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
  53. data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
  54. data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
  55. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
  56. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
  57. data/lib/active_storage_validations/matchers.rb +17 -21
  58. data/lib/active_storage_validations/pages_validator.rb +61 -0
  59. data/lib/active_storage_validations/processable_file_validator.rb +37 -0
  60. data/lib/active_storage_validations/railtie.rb +14 -0
  61. data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
  62. data/lib/active_storage_validations/shared/asv_analyzable.rb +89 -0
  63. data/lib/active_storage_validations/shared/asv_attachable.rb +236 -0
  64. data/lib/active_storage_validations/shared/asv_errorable.rb +64 -0
  65. data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
  66. data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
  67. data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
  68. data/lib/active_storage_validations/size_validator.rb +24 -41
  69. data/lib/active_storage_validations/total_size_validator.rb +52 -0
  70. data/lib/active_storage_validations/version.rb +1 -1
  71. data/lib/active_storage_validations.rb +27 -13
  72. metadata +113 -31
  73. data/lib/active_storage_validations/metadata.rb +0 -151
  74. data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
  75. data/lib/active_storage_validations/processable_image_validator.rb +0 -43
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_allow_blankable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_contextable"
7
+ require_relative "shared/asv_messageable"
8
+ require_relative "shared/asv_rspecable"
9
+ require_relative "shared/asv_validatable"
10
+
11
+ module ActiveStorageValidations
12
+ module Matchers
13
+ def validate_aspect_ratio_of(attribute_name)
14
+ AspectRatioValidatorMatcher.new(attribute_name)
15
+ end
16
+
17
+ class AspectRatioValidatorMatcher
18
+ include ASVActiveStorageable
19
+ include ASVAllowBlankable
20
+ include ASVAttachable
21
+ include ASVContextable
22
+ include ASVMessageable
23
+ include ASVRspecable
24
+ include ASVValidatable
25
+
26
+ def initialize(attribute_name)
27
+ initialize_allow_blankable
28
+ initialize_contextable
29
+ initialize_messageable
30
+ initialize_rspecable
31
+ @attribute_name = attribute_name
32
+ @allowed_aspect_ratios = @rejected_aspect_ratios = []
33
+ end
34
+
35
+ def description
36
+ "validate the aspect ratios allowed on :#{@attribute_name}."
37
+ end
38
+
39
+ def failure_message
40
+ "is expected to validate aspect ratio of :#{@attribute_name}"
41
+ end
42
+
43
+ def allowing(*aspect_ratios)
44
+ @allowed_aspect_ratios = aspect_ratios.flatten
45
+ self
46
+ end
47
+
48
+ def rejecting(*aspect_ratios)
49
+ @rejected_aspect_ratios = aspect_ratios.flatten
50
+ self
51
+ end
52
+
53
+ def matches?(subject)
54
+ @subject = subject.is_a?(Class) ? subject.new : subject
55
+
56
+ is_a_valid_active_storage_attribute? &&
57
+ is_context_valid? &&
58
+ is_allowing_blank? &&
59
+ is_custom_message_valid? &&
60
+ all_allowed_aspect_ratios_allowed? &&
61
+ all_rejected_aspect_ratios_rejected?
62
+ end
63
+
64
+ protected
65
+
66
+ def all_allowed_aspect_ratios_allowed?
67
+ @allowed_aspect_ratios_not_allowed ||= @allowed_aspect_ratios.reject { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
68
+ @allowed_aspect_ratios_not_allowed.empty?
69
+ end
70
+
71
+ def all_rejected_aspect_ratios_rejected?
72
+ @rejected_aspect_ratios_not_rejected ||= @rejected_aspect_ratios.select { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
73
+ @rejected_aspect_ratios_not_rejected.empty?
74
+ end
75
+
76
+ def aspect_ratio_allowed?(aspect_ratio)
77
+ width, height = valid_width_and_height_for(aspect_ratio)
78
+
79
+ mock_dimensions_for(attach_file, width, height) do
80
+ validate
81
+ detach_file
82
+ is_valid?
83
+ end
84
+ end
85
+
86
+ def is_custom_message_valid?
87
+ return true unless @custom_message
88
+
89
+ mock_dimensions_for(attach_file, -1, -1) do
90
+ validate
91
+ detach_file
92
+ has_an_error_message_which_is_custom_message?
93
+ end
94
+ end
95
+
96
+ def mock_dimensions_for(attachment, width, height)
97
+ Matchers.mock_metadata(attachment, { width: width, height: height }) do
98
+ yield
99
+ end
100
+ end
101
+
102
+ def valid_width_and_height_for(aspect_ratio)
103
+ case aspect_ratio
104
+ when :square then [ 100, 100 ]
105
+ when :portrait then [ 100, 200 ]
106
+ when :landscape then [ 200, 100 ]
107
+ when validator_class::ASPECT_RATIO_REGEX
108
+ aspect_ratio =~ validator_class::ASPECT_RATIO_REGEX
109
+ x = Regexp.last_match(1).to_i
110
+ y = Regexp.last_match(2).to_i
111
+
112
+ [ 100 * x, 100 * y ]
113
+ else
114
+ [ -1, -1 ]
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,58 +1,81 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_attachable"
5
+ require_relative "shared/asv_contextable"
6
+ require_relative "shared/asv_messageable"
7
+ require_relative "shared/asv_rspecable"
8
+ require_relative "shared/asv_validatable"
9
+
3
10
  module ActiveStorageValidations
4
11
  module Matchers
5
- def validate_attached_of(name)
6
- AttachedValidatorMatcher.new(name)
12
+ def validate_attached_of(attribute_name)
13
+ AttachedValidatorMatcher.new(attribute_name)
7
14
  end
8
15
 
9
16
  class AttachedValidatorMatcher
17
+ include ASVActiveStorageable
18
+ include ASVAttachable
19
+ include ASVContextable
20
+ include ASVMessageable
21
+ include ASVRspecable
22
+ include ASVValidatable
23
+
10
24
  def initialize(attribute_name)
25
+ initialize_contextable
26
+ initialize_messageable
27
+ initialize_rspecable
11
28
  @attribute_name = attribute_name
12
29
  end
13
30
 
14
31
  def description
15
- "validate #{@attribute_name} must be attached"
32
+ "validate that :#{@attribute_name} must be attached"
33
+ end
34
+
35
+ def failure_message
36
+ "is expected to validate attachment of :#{@attribute_name}"
16
37
  end
17
38
 
18
39
  def matches?(subject)
19
40
  @subject = subject.is_a?(Class) ? subject.new : subject
20
- responds_to_methods && valid_when_attached && invalid_when_not_attached
41
+
42
+ is_a_valid_active_storage_attribute? &&
43
+ is_context_valid? &&
44
+ is_custom_message_valid? &&
45
+ is_valid_when_file_attached? &&
46
+ is_invalid_when_file_not_attached?
21
47
  end
22
48
 
23
- def failure_message
24
- "is expected to validate attached of #{@attribute_name}"
49
+ private
50
+
51
+ def is_valid_when_file_attached?
52
+ attach_file unless file_attached?
53
+ validate
54
+ is_valid?
25
55
  end
26
56
 
27
- def failure_message_when_negated
28
- "is expected to not validate attached of #{@attribute_name}"
57
+ def is_invalid_when_file_not_attached?
58
+ detach_file if file_attached?
59
+ validate
60
+ !is_valid?
29
61
  end
30
62
 
31
- private
63
+ def is_custom_message_valid?
64
+ return true unless @custom_message
32
65
 
33
- def responds_to_methods
34
- @subject.respond_to?(@attribute_name) &&
35
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
36
- @subject.public_send(@attribute_name).respond_to?(:detach)
66
+ detach_file if file_attached?
67
+ validate
68
+ has_an_error_message_which_is_custom_message?
37
69
  end
38
70
 
39
- def valid_when_attached
40
- @subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
41
- @subject.validate
42
- @subject.errors.details[@attribute_name].exclude?(error: :blank)
71
+ def file_attached?
72
+ @subject.public_send(@attribute_name).attached?
43
73
  end
44
74
 
45
- def invalid_when_not_attached
75
+ def detach_file
46
76
  @subject.public_send(@attribute_name).detach
47
77
  # Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
48
78
  @subject.public_send("#{@attribute_name}=", nil)
49
-
50
- @subject.validate
51
- @subject.errors.details[@attribute_name].include?(error: :blank)
52
- end
53
-
54
- def attachable
55
- { io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
56
79
  end
57
80
  end
58
81
  end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Big thank you to the paperclip validation matchers:
4
+ # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
5
+
6
+ require_relative "shared/asv_active_storageable"
7
+ require_relative "shared/asv_allow_blankable"
8
+ require_relative "shared/asv_attachable"
9
+ require_relative "shared/asv_contextable"
10
+ require_relative "shared/asv_messageable"
11
+ require_relative "shared/asv_rspecable"
12
+ require_relative "shared/asv_validatable"
13
+
14
+ module ActiveStorageValidations
15
+ module Matchers
16
+ class BaseComparisonValidatorMatcher
17
+ # BaseComparisonValidatorMatcher is an abstract class and shouldn't be instantiated directly.
18
+
19
+ include ASVActiveStorageable
20
+ include ASVAllowBlankable
21
+ include ASVAttachable
22
+ include ASVContextable
23
+ include ASVMessageable
24
+ include ASVRspecable
25
+ include ASVValidatable
26
+
27
+ def initialize(attribute_name)
28
+ initialize_allow_blankable
29
+ initialize_contextable
30
+ initialize_messageable
31
+ initialize_rspecable
32
+ @attribute_name = attribute_name
33
+ @min = @max = nil
34
+ end
35
+
36
+ def less_than(value)
37
+ @max = value - smallest_measurement
38
+ self
39
+ end
40
+
41
+ def less_than_or_equal_to(value)
42
+ @max = value
43
+ self
44
+ end
45
+
46
+ def greater_than(value)
47
+ @min = value + smallest_measurement
48
+ self
49
+ end
50
+
51
+ def greater_than_or_equal_to(value)
52
+ @min = value
53
+ self
54
+ end
55
+
56
+ def between(range)
57
+ @min, @max = range.first, range.last
58
+ self
59
+ end
60
+
61
+ def equal_to(value)
62
+ @exact = value
63
+ self
64
+ end
65
+
66
+ def matches?(subject)
67
+ @subject = subject.is_a?(Class) ? subject.new : subject
68
+
69
+ is_a_valid_active_storage_attribute? &&
70
+ is_context_valid? &&
71
+ is_allowing_blank? &&
72
+ is_custom_message_valid? &&
73
+ not_lower_than_min? &&
74
+ higher_than_min? &&
75
+ lower_than_max? &&
76
+ not_higher_than_max? &&
77
+ equal_to_exact?
78
+ end
79
+
80
+ protected
81
+
82
+ def build_failure_message(message)
83
+ return unless @failure_message_artefacts.present?
84
+
85
+ message << " but there seem to have issues with the matcher methods you used, since:"
86
+ @failure_message_artefacts.each do |error_case|
87
+ message << " validation failed when provided with a #{error_case[:value]} #{failure_message_unit} test file"
88
+ end
89
+ message << " whereas it should have passed"
90
+ end
91
+
92
+ def failure_message_unit
93
+ raise NotImplementedError
94
+ end
95
+
96
+ def not_lower_than_min?
97
+ @min.nil? || !passes_validation_with_value(@min - 1)
98
+ end
99
+
100
+ def higher_than_min?
101
+ @min.nil? || passes_validation_with_value(@min + 1)
102
+ end
103
+
104
+ def lower_than_max?
105
+ @max.nil? || @max == Float::INFINITY || passes_validation_with_value(@max - 1)
106
+ end
107
+
108
+ def not_higher_than_max?
109
+ @max.nil? || @max == Float::INFINITY || !passes_validation_with_value(@max + 1)
110
+ end
111
+
112
+ def equal_to_exact?
113
+ @exact.nil? || passes_validation_with_value(@exact)
114
+ end
115
+
116
+ def smallest_measurement
117
+ raise NotImplementedError
118
+ end
119
+
120
+ def passes_validation_with_value(value)
121
+ mock_value_for(io, value) do
122
+ attach_file
123
+ validate
124
+ detach_file
125
+ is_valid? || add_failure_message_artefact(value)
126
+ end
127
+ end
128
+
129
+ def add_failure_message_artefact(value)
130
+ @failure_message_artefacts << { value: value }
131
+ false
132
+ end
133
+
134
+ def is_custom_message_valid?
135
+ return true unless @custom_message
136
+
137
+ mock_value_for(io, -smallest_measurement) do
138
+ attach_file
139
+ validate
140
+ detach_file
141
+ has_an_error_message_which_is_custom_message?
142
+ end
143
+ end
144
+
145
+ def mock_value_for(io, size)
146
+ raise NotImplementedError
147
+ end
148
+ end
149
+ end
150
+ end
@@ -2,87 +2,146 @@
2
2
 
3
3
  # Big thank you to the paperclip validation matchers:
4
4
  # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
5
+
6
+ require_relative "shared/asv_active_storageable"
7
+ require_relative "shared/asv_allow_blankable"
8
+ require_relative "shared/asv_attachable"
9
+ require_relative "shared/asv_contextable"
10
+ require_relative "shared/asv_messageable"
11
+ require_relative "shared/asv_rspecable"
12
+ require_relative "shared/asv_validatable"
13
+
5
14
  module ActiveStorageValidations
6
15
  module Matchers
7
- def validate_content_type_of(name)
8
- ContentTypeValidatorMatcher.new(name)
16
+ def validate_content_type_of(attribute_name)
17
+ ContentTypeValidatorMatcher.new(attribute_name)
9
18
  end
10
19
 
11
20
  class ContentTypeValidatorMatcher
21
+ include ASVActiveStorageable
22
+ include ASVAllowBlankable
23
+ include ASVAttachable
24
+ include ASVContextable
25
+ include ASVMessageable
26
+ include ASVRspecable
27
+ include ASVValidatable
28
+
12
29
  def initialize(attribute_name)
30
+ initialize_allow_blankable
31
+ initialize_contextable
32
+ initialize_messageable
33
+ initialize_rspecable
13
34
  @attribute_name = attribute_name
35
+ @allowed_content_types = @rejected_content_types = []
14
36
  end
15
37
 
16
38
  def description
17
- "validate the content types allowed on attachment #{@attribute_name}"
39
+ "validate the content types allowed on :#{@attribute_name}"
40
+ end
41
+
42
+ def failure_message
43
+ message = [ "is expected to validate the content types of :#{@attribute_name}" ]
44
+ build_failure_message(message)
45
+ message.join("\n")
18
46
  end
19
47
 
20
- def allowing(*types)
21
- @allowed_types = types.flatten
48
+ def allowing(*content_types)
49
+ types = content_types.flatten
50
+ @allowed_content_types = types.map { |content_type| normalize_content_type(content_type) }.flatten
22
51
  self
23
52
  end
24
53
 
25
- def rejecting(*types)
26
- @rejected_types = types.flatten
54
+ def rejecting(*content_types)
55
+ types = content_types.flatten
56
+ @rejected_content_types = types.map { |content_type| normalize_content_type(content_type) }.flatten
27
57
  self
28
58
  end
29
59
 
30
60
  def matches?(subject)
31
61
  @subject = subject.is_a?(Class) ? subject.new : subject
32
- responds_to_methods && allowed_types_allowed? && rejected_types_rejected?
62
+
63
+ is_a_valid_active_storage_attribute? &&
64
+ is_context_valid? &&
65
+ is_allowing_blank? &&
66
+ is_custom_message_valid? &&
67
+ all_allowed_content_types_allowed? &&
68
+ all_rejected_content_types_rejected?
33
69
  end
34
70
 
35
- def failure_message
36
- message = ["Expected #{@attribute_name}"]
71
+ protected
37
72
 
38
- if @allowed_types
39
- message << "Accept content types: #{allowed_types.join(", ")}"
40
- message << "#{@missing_allowed_types.join(", ")} were rejected"
73
+ def build_failure_message(message)
74
+ if @allowed_content_types_not_allowed.present?
75
+ message << " the following content type#{'s' if @allowed_content_types.count > 1} should be allowed: :#{@allowed_content_types.join(", :")}"
76
+ message << " but #{pluralize(@allowed_content_types_not_allowed)} rejected"
41
77
  end
42
78
 
43
- if @rejected_types
44
- message << "Reject content types: #{rejected_types.join(", ")}"
45
- message << "#{@missing_rejected_types.join(", ")} were accepted"
79
+ if @rejected_content_types_not_rejected.present?
80
+ message << " the following content type#{'s' if @rejected_content_types.count > 1} should be rejected: :#{@rejected_content_types.join(", :")}"
81
+ message << " but #{pluralize(@rejected_content_types_not_rejected)} accepted"
46
82
  end
83
+ end
47
84
 
48
- message.join("\n")
85
+ def pluralize(types)
86
+ if types.count == 1
87
+ ":#{types[0]} was"
88
+ else
89
+ ":#{types.join(", :")} were"
90
+ end
49
91
  end
50
92
 
51
- protected
93
+ def normalize_content_type(content_type)
94
+ Marcel::MimeType.for(declared_type: content_type.to_s, extension: content_type.to_s)
95
+ end
52
96
 
53
- def responds_to_methods
54
- @subject.respond_to?(@attribute_name) &&
55
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
56
- @subject.public_send(@attribute_name).respond_to?(:detach)
97
+ def all_allowed_content_types_allowed?
98
+ @allowed_content_types_not_allowed ||= @allowed_content_types.reject { |type| type_allowed?(type) }
99
+ @allowed_content_types_not_allowed.empty?
57
100
  end
58
101
 
59
- def allowed_types
60
- @allowed_types || []
102
+ def all_rejected_content_types_rejected?
103
+ @rejected_content_types_not_rejected ||= @rejected_content_types.select { |type| type_allowed?(type) }
104
+ @rejected_content_types_not_rejected.empty?
61
105
  end
62
106
 
63
- def rejected_types
64
- @rejected_types || []
107
+ def type_allowed?(content_type)
108
+ attach_file_with_content_type(content_type)
109
+ validate
110
+ detach_file
111
+ is_valid?
65
112
  end
66
113
 
67
- def allowed_types_allowed?
68
- @missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
69
- @missing_allowed_types.none?
114
+ def attach_file_with_content_type(content_type)
115
+ @subject.public_send(@attribute_name).attach(attachment_for(content_type))
70
116
  end
71
117
 
72
- def rejected_types_rejected?
73
- @missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
74
- @missing_rejected_types.none?
118
+ def is_custom_message_valid?
119
+ return true unless @custom_message
120
+
121
+ attach_invalid_content_type_file
122
+ validate
123
+ has_an_error_message_which_is_custom_message?
124
+ end
125
+
126
+ def attach_invalid_content_type_file
127
+ @subject.public_send(@attribute_name).attach(attachment_for("fake/fake"))
75
128
  end
76
129
 
77
- def type_allowed?(type)
78
- @subject.public_send(@attribute_name).attach(attachment_for(type))
79
- @subject.validate
80
- @subject.errors.details[@attribute_name].all? { |error| error[:error] != :content_type_invalid }
130
+ def attachment_for(content_type)
131
+ suffix = Marcel::TYPE_EXTS[content_type.to_s]&.first || "fake"
132
+
133
+ {
134
+ io: Tempfile.new("."),
135
+ filename: "test.#{suffix}",
136
+ content_type: content_type
137
+ }
81
138
  end
82
139
 
83
- def attachment_for(type)
84
- suffix = type.to_s.split('/').last
85
- { io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
140
+ # Due to the way we build test attachments in #attachment_for
141
+ # (ie spoofed file basically), we need to ignore the error related to
142
+ # content type spoofing in our matcher to pass the tests
143
+ def validator_errors_for_attribute
144
+ super.reject { |hash| hash[:error] == :content_type_spoofed }
86
145
  end
87
146
  end
88
147
  end