diff --git a/README.md b/README.md index abe4b5b4..bf72a55e 100755 --- a/README.md +++ b/README.md @@ -297,7 +297,8 @@ Very simple example of validation with file attached, content type check and cus [![Sample](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png) ## Test matchers -Provides RSpec-compatible and Minitest-compatible matchers for testing the validators. Only `aspect_ratio`, `attached`, `content_type`, `dimension`, `size` and `total_size` validators currently have their matcher developed. + +Provides RSpec-compatible and Minitest-compatible matchers for testing the validators. Only `aspect_ratio`, `attached`, `content_type`, `processable_image`, `dimension`, `size` and `total_size` validators currently have their matcher developed. ### RSpec @@ -327,6 +328,9 @@ describe User do # attached it { is_expected.to validate_attached_of(:avatar) } + # processable_image + it { is_expected.to validate_processable_image_of(:avatar) } + # content_type: # #allowing, #rejecting it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') } diff --git a/lib/active_storage_validations/matchers.rb b/lib/active_storage_validations/matchers.rb index f4cb2a97..a77f1d8a 100644 --- a/lib/active_storage_validations/matchers.rb +++ b/lib/active_storage_validations/matchers.rb @@ -2,6 +2,7 @@ require 'active_storage_validations/matchers/aspect_ratio_validator_matcher' require 'active_storage_validations/matchers/attached_validator_matcher' +require 'active_storage_validations/matchers/processable_image_validator_matcher' require 'active_storage_validations/matchers/content_type_validator_matcher' require 'active_storage_validations/matchers/dimension_validator_matcher' require 'active_storage_validations/matchers/size_validator_matcher' diff --git a/lib/active_storage_validations/matchers/attached_validator_matcher.rb b/lib/active_storage_validations/matchers/attached_validator_matcher.rb index a29f6f30..7f4b8716 100644 --- a/lib/active_storage_validations/matchers/attached_validator_matcher.rb +++ b/lib/active_storage_validations/matchers/attached_validator_matcher.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'concerns/active_storageable.rb' +require_relative 'concerns/attachable.rb' require_relative 'concerns/contextable.rb' require_relative 'concerns/messageable.rb' require_relative 'concerns/rspecable.rb' @@ -14,6 +15,7 @@ def validate_attached_of(attribute_name) class AttachedValidatorMatcher include ActiveStorageable + include Attachable include Contextable include Messageable include Rspecable @@ -47,7 +49,7 @@ def matches?(subject) private def is_valid_when_file_attached? - attach_dummy_file unless file_attached? + attach_file unless file_attached? validate is_valid? end @@ -66,16 +68,6 @@ def is_custom_message_valid? has_an_error_message_which_is_custom_message? end - def attach_dummy_file - dummy_file = { - io: Tempfile.new('.'), - filename: 'dummy.txt', - content_type: 'text/plain' - } - - @subject.public_send(@attribute_name).attach(dummy_file) - end - def file_attached? @subject.public_send(@attribute_name).attached? end diff --git a/lib/active_storage_validations/matchers/concerns/attachable.rb b/lib/active_storage_validations/matchers/concerns/attachable.rb index 941825c6..9bbe3136 100644 --- a/lib/active_storage_validations/matchers/concerns/attachable.rb +++ b/lib/active_storage_validations/matchers/concerns/attachable.rb @@ -3,8 +3,8 @@ module Matchers module Attachable private - def attach_file - @subject.public_send(@attribute_name).attach(dummy_file) + def attach_file(file = dummy_file) + @subject.public_send(@attribute_name).attach(file) @subject.public_send(@attribute_name) end @@ -16,6 +16,22 @@ def dummy_file } end + def processable_image + { + io: File.open(Rails.root.join('public', 'image_1920x1080.png')), + filename: 'image_1920x1080_file.png', + content_type: 'image/png' + } + end + + def not_processable_image + { + io: Tempfile.new('.'), + filename: 'processable.txt', + content_type: 'text/plain' + } + end + def io @io ||= Tempfile.new('Hello world!') end @@ -23,6 +39,10 @@ def io def detach_file @subject.attachment_changes.delete(@attribute_name.to_s) end + + def file_attached? + @subject.public_send(@attribute_name).attached? + end end end end diff --git a/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb b/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb new file mode 100644 index 00000000..6e816965 --- /dev/null +++ b/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require_relative 'concerns/active_storageable.rb' +require_relative 'concerns/allow_blankable.rb' +require_relative 'concerns/attachable.rb' +require_relative 'concerns/contextable.rb' +require_relative 'concerns/messageable.rb' +require_relative 'concerns/rspecable.rb' +require_relative 'concerns/validatable.rb' + +module ActiveStorageValidations + module Matchers + def validate_processable_image_of(name) + ProcessableImageValidatorMatcher.new(name) + end + + class ProcessableImageValidatorMatcher + include ActiveStorageable + include AllowBlankable + include Attachable + include Contextable + include Messageable + include Rspecable + include Validatable + + def initialize(attribute_name) + initialize_allow_blankable + initialize_contextable + initialize_messageable + initialize_rspecable + @attribute_name = attribute_name + end + + def description + "validate that :#{@attribute_name} is a processable image" + end + + def failure_message + "is expected to validate the processable image of :#{@attribute_name}" + end + + def matches?(subject) + @subject = subject.is_a?(Class) ? subject.new : subject + + is_a_valid_active_storage_attribute? && + is_context_valid? && + is_custom_message_valid? && + is_valid_when_image_processable? && + is_invalid_when_image_not_processable? + end + + private + + def is_valid_when_image_processable? + attach_file(processable_image) + validate + detach_file + is_valid? + end + + def is_invalid_when_image_not_processable? + attach_file(not_processable_image) + validate + detach_file + !is_valid? + end + + def is_custom_message_valid? + return true unless @custom_message + + attach_file(not_processable_image) + validate + detach_file + has_an_error_message_which_is_custom_message? + end + end + end +end diff --git a/test/dummy/app/models/processable_image/matcher.rb b/test/dummy/app/models/processable_image/matcher.rb new file mode 100644 index 00000000..c8a8067a --- /dev/null +++ b/test/dummy/app/models/processable_image/matcher.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: processable_image_matchers +# +# title :string +# id :integer not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# + +class ProcessableImage::Matcher < ApplicationRecord + include Validatable + + has_one_attached :custom_matcher + validates :custom_matcher, processable_image: true + + has_one_attached :processable + validates :processable, processable_image: true + + has_one_attached :with_message + validates :with_message, processable_image: { message: 'Custom message' } + + has_one_attached :with_context_symbol + validates :with_context_symbol, processable_image: true, on: :update + has_one_attached :with_context_array + validates :with_context_array, processable_image: true, on: %i[update custom] + has_one_attached :with_several_validators_and_contexts + validates :with_several_validators_and_contexts, processable_image: true, on: :update + validates :with_several_validators_and_contexts, processable_image: true, on: :custom + + has_one_attached :as_instance + validates :as_instance, processable_image: true + + has_one_attached :validatable_different_error_messages + validates :validatable_different_error_messages, processable_image: { message: 'Custom message 1' }, if: :title_is_quo_vadis? + validates :validatable_different_error_messages, processable_image: { message: 'Custom message 2' }, if: :title_is_american_psycho? + + has_one_attached :failure_message + validates :failure_message, processable_image: true + has_one_attached :failure_message_when_negated + validates :failure_message_when_negated, processable_image: true + + has_one_attached :not_processable +end diff --git a/test/matchers/processable_image_validator_matcher_test.rb b/test/matchers/processable_image_validator_matcher_test.rb new file mode 100644 index 00000000..15328894 --- /dev/null +++ b/test/matchers/processable_image_validator_matcher_test.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'test_helper' +require 'matchers/shared_examples/checks_if_is_a_valid_active_storage_attribute' +require 'matchers/shared_examples/checks_if_is_valid' +require 'matchers/shared_examples/has_custom_matcher' +require 'matchers/shared_examples/has_valid_rspec_message_methods' +require 'matchers/shared_examples/works_with_both_instance_and_class' +require 'matchers/shared_examples/works_with_context' +require 'matchers/shared_examples/works_with_custom_message' + +describe ActiveStorageValidations::Matchers::ProcessableImageValidatorMatcher do + include MatcherHelpers + + include ChecksIfIsAValidActiveStorageAttribute + include ChecksIfIsValid + include HasCustomMatcher + include HasValidRspecMessageMethods + include WorksWithBothInstanceAndClass + + let(:matcher) { ActiveStorageValidations::Matchers::ProcessableImageValidatorMatcher.new(model_attribute) } + let(:klass) { ProcessableImage::Matcher } + + describe "#validate_processable_image_of" do + include HasCustomMatcher + end + + describe 'when the passed model attribute does not have a `processable: true` constraint' do + subject { matcher } + + let(:model_attribute) { :not_required } + + it { is_expected_not_to_match_for(klass) } + end + + describe '#with_message' do + include WorksWithCustomMessage + end + + describe "#on" do + include WorksWithContext + end +end diff --git a/test/matchers/shared_examples/checks_if_is_valid.rb b/test/matchers/shared_examples/checks_if_is_valid.rb index facd924d..b1a8b2e9 100644 --- a/test/matchers/shared_examples/checks_if_is_valid.rb +++ b/test/matchers/shared_examples/checks_if_is_valid.rb @@ -9,6 +9,7 @@ module ChecksIfIsValid case validator_sym when :aspect_ratio then matcher.allowing(:square) when :attached then matcher + when :processable_image then matcher when :content_type then matcher.rejecting('image/jpg') when :dimension then matcher.width(150) when :size then matcher.less_than(10.megabytes) diff --git a/test/matchers/shared_examples/has_custom_matcher.rb b/test/matchers/shared_examples/has_custom_matcher.rb index e8096c87..0c97eeee 100644 --- a/test/matchers/shared_examples/has_custom_matcher.rb +++ b/test/matchers/shared_examples/has_custom_matcher.rb @@ -13,6 +13,7 @@ class ActiveSupport::TestCase case validator_sym when :aspect_ratio then custom_matcher_builder.validate_aspect_ratio_of(model_attribute).allowing(:square) when :attached then custom_matcher_builder.validate_attached_of(model_attribute) + when :processable_image then custom_matcher_builder.validate_processable_image_of(model_attribute) when :content_type then custom_matcher_builder.validate_content_type_of(model_attribute).allowing('image/png') when :dimension then custom_matcher_builder.validate_dimensions_of(model_attribute).width(150).height(150) when :size then custom_matcher_builder.validate_size_of(model_attribute).less_than_or_equal_to(5.megabytes) diff --git a/test/matchers/shared_examples/has_valid_rspec_message_methods.rb b/test/matchers/shared_examples/has_valid_rspec_message_methods.rb index dda891a6..15720608 100644 --- a/test/matchers/shared_examples/has_valid_rspec_message_methods.rb +++ b/test/matchers/shared_examples/has_valid_rspec_message_methods.rb @@ -7,6 +7,7 @@ module HasValidRspecMessageMethods case validator_sym when :aspect_ratio then matcher.rejecting(:square) when :attached then matcher + when :processable_image then matcher when :content_type then matcher.rejecting('image/png') when :dimension then matcher.width(75).height(75) when :size then matcher.less_than_or_equal_to(7.megabytes) @@ -27,6 +28,10 @@ module HasValidRspecMessageMethods <<~FAILURE_MESSAGE is expected to validate attachment of :#{model_attribute} FAILURE_MESSAGE + when :processable_image + <<~FAILURE_MESSAGE + is expected to validate the processable image of :#{model_attribute} + FAILURE_MESSAGE when :content_type <<~FAILURE_MESSAGE is expected to validate the content types of :#{model_attribute} @@ -67,6 +72,7 @@ module HasValidRspecMessageMethods case validator_sym when :aspect_ratio then matcher.allowing(:square) when :attached then matcher + when :processable_image then matcher when :content_type then matcher.allowing('image/png') when :dimension then matcher.width(150).height(150) when :size then matcher.less_than_or_equal_to(5.megabytes) @@ -87,6 +93,10 @@ module HasValidRspecMessageMethods <<~FAILURE_MESSAGE is expected not to validate attachment of :#{model_attribute} FAILURE_MESSAGE + when :processable_image + <<~FAILURE_MESSAGE + is expected not to validate the processable image of :#{model_attribute} + FAILURE_MESSAGE when :content_type <<~FAILURE_MESSAGE is expected not to validate the content types of :#{model_attribute} diff --git a/test/matchers/shared_examples/works_with_both_instance_and_class.rb b/test/matchers/shared_examples/works_with_both_instance_and_class.rb index b8e6ff58..a6f41def 100644 --- a/test/matchers/shared_examples/works_with_both_instance_and_class.rb +++ b/test/matchers/shared_examples/works_with_both_instance_and_class.rb @@ -7,6 +7,7 @@ module WorksWithBothInstanceAndClass case validator_sym when :aspect_ratio then matcher.allowing(:square) when :attached then nil + when :processable_image then nil when :content_type then matcher.allowing('image/png') when :dimension then matcher.width(150).height(150) when :size then matcher.less_than_or_equal_to(5.megabytes) diff --git a/test/matchers/shared_examples/works_with_custom_message.rb b/test/matchers/shared_examples/works_with_custom_message.rb index b6a172cc..c02398db 100644 --- a/test/matchers/shared_examples/works_with_custom_message.rb +++ b/test/matchers/shared_examples/works_with_custom_message.rb @@ -9,6 +9,7 @@ module WorksWithCustomMessage case validator_sym when :aspect_ratio then matcher.allowing(:square) when :attached then nil + when :processable_image then nil when :content_type then matcher.allowing('image/png') when :dimension then matcher.width(150).height(150) when :size then matcher.less_than_or_equal_to(5.megabytes)