module Shoulda module Matchers module ActiveModel # The `have_secure_password` matcher tests usage of the # `has_secure_password` macro. # # #### Example # # class User # include ActiveModel::Model # include ActiveModel::SecurePassword # attr_accessor :password # attr_accessor :reset_password # # has_secure_password # has_secure_password :reset_password # end # # # RSpec # RSpec.describe User, type: :model do # it { should have_secure_password } # it { should have_secure_password(:reset_password) } # end # # # Minitest (Shoulda) # class UserTest < ActiveSupport::TestCase # should have_secure_password # should have_secure_password(:reset_password) # end # # @return [HaveSecurePasswordMatcher] # def have_secure_password(attr = :password) HaveSecurePasswordMatcher.new(attr) end # @private class HaveSecurePasswordMatcher attr_reader :failure_message CORRECT_PASSWORD = 'aBcDe12345'.freeze INCORRECT_PASSWORD = 'password'.freeze MESSAGES = { authenticated_incorrect_password: 'expected %{subject} to not'\ ' authenticate an incorrect %{attribute}', did_not_authenticate_correct_password: 'expected %{subject} to'\ ' authenticate the correct %{attribute}', method_not_found: 'expected %{subject} to respond to %{methods}', should_not_have_secure_password: 'expected %{subject} to'\ ' not %{description}!', }.freeze def initialize(attribute) @attribute = attribute.to_sym end def description "have a secure password, defined on #{@attribute} attribute" end def matches?(subject) @subject = subject if failure = validate key, params = failure @failure_message = MESSAGES[key] % { subject: subject.class }.merge(params) end failure.nil? end def failure_message_when_negated MESSAGES[:should_not_have_secure_password] % { subject: @subject.class, description: description } end protected attr_reader :subject def validate missing_methods = expected_methods.reject do |m| subject.respond_to?(m) end if missing_methods.present? [:method_not_found, { methods: missing_methods.to_sentence }] else subject.send("#{@attribute}=", CORRECT_PASSWORD) subject.send("#{@attribute}_confirmation=", CORRECT_PASSWORD) if not subject.send(authenticate_method, CORRECT_PASSWORD) [:did_not_authenticate_correct_password, { attribute: @attribute },] elsif subject.send(authenticate_method, INCORRECT_PASSWORD) [:authenticated_incorrect_password, { attribute: @attribute }] end end end private def expected_methods @_expected_methods ||= %I[ #{authenticate_method} #{@attribute}= #{@attribute}_confirmation= #{@attribute}_digest #{@attribute}_digest= ] end def authenticate_method if @attribute == :password :authenticate else "authenticate_#{@attribute}".to_sym end end end end end end