Skip to content

Commit

Permalink
feat: Add encrypt_matcher to test usage of the encrypts method (#1581)
Browse files Browse the repository at this point in the history
  • Loading branch information
theforestvn88 committed Jan 2, 2024
1 parent b8d04ee commit 2a2b062
Show file tree
Hide file tree
Showing 4 changed files with 385 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -411,6 +411,8 @@ about any of them, make sure to [consult the documentation][rubydocs]!
tests usage of `validates_uniqueness_of`.
* **[normalize](lib/shoulda/matchers/active_record/normalize_matcher.rb)** tests
usage of the `normalize` macro
* **[encrypt](lib/shoulda/matchers/active_record/encrypt_matcher.rb)**
tests usage of the `encrypts` macro.

### ActionController matchers

Expand Down
1 change: 1 addition & 0 deletions lib/shoulda/matchers/active_record.rb
Expand Up @@ -25,6 +25,7 @@
require 'shoulda/matchers/active_record/validate_uniqueness_of_matcher'
require 'shoulda/matchers/active_record/have_attached_matcher'
require 'shoulda/matchers/active_record/normalize_matcher'
require 'shoulda/matchers/active_record/encrypt_matcher'

module Shoulda
module Matchers
Expand Down
174 changes: 174 additions & 0 deletions lib/shoulda/matchers/active_record/encrypt_matcher.rb
@@ -0,0 +1,174 @@
module Shoulda
module Matchers
module ActiveRecord
# The `encrypt` matcher tests usage of the
# `encrypts` macro (Rails 7+ only).
#
# class Survey < ActiveRecord::Base
# encrypts :access_code
# end
#
# # RSpec
# RSpec.describe Survey, type: :model do
# it { should encrypt(:access_code) }
# end
#
# # Minitest (Shoulda)
# class SurveyTest < ActiveSupport::TestCase
# should encrypt(:access_code)
# end
#
# #### Qualifiers
#
# ##### deterministic
#
# class Survey < ActiveRecord::Base
# encrypts :access_code, deterministic: true
# end
#
# # RSpec
# RSpec.describe Survey, type: :model do
# it { should encrypt(:access_code).deterministic(true) }
# end
#
# # Minitest (Shoulda)
# class SurveyTest < ActiveSupport::TestCase
# should encrypt(:access_code).deterministic(true)
# end
#
# ##### downcase
#
# class Survey < ActiveRecord::Base
# encrypts :access_code, downcase: true
# end
#
# # RSpec
# RSpec.describe Survey, type: :model do
# it { should encrypt(:access_code).downcase(true) }
# end
#
# # Minitest (Shoulda)
# class SurveyTest < ActiveSupport::TestCase
# should encrypt(:access_code).downcase(true)
# end
#
# ##### ignore_case
#
# class Survey < ActiveRecord::Base
# encrypts :access_code, deterministic: true, ignore_case: true
# end
#
# # RSpec
# RSpec.describe Survey, type: :model do
# it { should encrypt(:access_code).ignore_case(true) }
# end
#
# # Minitest (Shoulda)
# class SurveyTest < ActiveSupport::TestCase
# should encrypt(:access_code).ignore_case(true)
# end
#
# @return [EncryptMatcher]
#
def encrypt(value)
EncryptMatcher.new(value)
end

# @private
class EncryptMatcher
def initialize(attribute)
@attribute = attribute.to_sym
@options = {}
end

attr_reader :failure_message, :failure_message_when_negated

def deterministic(deterministic)
with_option(:deterministic, deterministic)
end

def downcase(downcase)
with_option(:downcase, downcase)
end

def ignore_case(ignore_case)
with_option(:ignore_case, ignore_case)
end

def matches?(subject)
@subject = subject
result = encrypted_attributes_included? &&
options_correct?(
:deterministic,
:downcase,
:ignore_case,
)

if result
@failure_message_when_negated = "Did not expect to #{description} of #{class_name}"
if @options.present?
@failure_message_when_negated += "
using "
@failure_message_when_negated += @options.map { |opt, expected|
":#{opt} option as ‹#{expected}›"
}.join(' and
')
end

@failure_message_when_negated += ",
but it did"
end

result
end

def description
"encrypt :#{@attribute}"
end

private

def encrypted_attributes_included?
if encrypted_attributes.include?(@attribute)
true
else
@failure_message = "Expected to #{description} of #{class_name}, but it did not"
false
end
end

def with_option(option_name, value)
@options[option_name] = value
self
end

def options_correct?(*opts)
opts.all? do |opt|
next true unless @options.key?(opt)

expected = @options[opt]
actual = encrypted_attribute_scheme.send("#{opt}?")
next true if expected == actual

@failure_message = "Expected to #{description} of #{class_name} using :#{opt} option
as ‹#{expected}›, but got ‹#{actual}›"

false
end
end

def encrypted_attributes
@_encrypted_attributes ||= @subject.class.encrypted_attributes || []
end

def encrypted_attribute_scheme
@subject.class.type_for_attribute(@attribute).scheme
end

def class_name
@subject.class.name
end
end
end
end
end

0 comments on commit 2a2b062

Please sign in to comment.