Add matcher for has_secure_token

This commit is contained in:
Faraz Yashar 2017-06-29 03:10:21 -04:00
parent 4b160bd19e
commit bf38660507
5 changed files with 286 additions and 0 deletions

View File

@ -13,6 +13,7 @@ require "shoulda/matchers/active_record/association_matchers/option_verifier"
require "shoulda/matchers/active_record/have_db_column_matcher"
require "shoulda/matchers/active_record/have_db_index_matcher"
require "shoulda/matchers/active_record/have_readonly_attribute_matcher"
require "shoulda/matchers/active_record/have_secure_token_matcher"
require "shoulda/matchers/active_record/serialize_matcher"
require "shoulda/matchers/active_record/accept_nested_attributes_for_matcher"
require "shoulda/matchers/active_record/define_enum_for_matcher"

View File

@ -0,0 +1,111 @@
module Shoulda
module Matchers
module ActiveRecord
# The `have_secure_token` matcher tests usage of the
# `has_secure_token` macro.
#
# #### Example
#
# class User < ActiveRecord
# attr_accessor :token
# attr_accessor :auth_token
#
# has_secure_token
# has_secure_token :auth_token
# end
#
# # RSpec
# RSpec.describe User, type: :model do
# it { should have_secure_token }
# it { should have_secure_token(:auth_token) }
# end
#
# # Minitest (Shoulda)
# class UserTest < ActiveSupport::TestCase
# should have_secure_token
# should have_secure_token(:auth_token)
# end
#
# @return [HaveSecureToken]
#
# rubocop:disable Style/PredicateName
def have_secure_token(token_attribute = :token)
HaveSecureTokenMatcher.new(token_attribute)
end
# rubocop:enable Style/PredicateName
# @private
class HaveSecureTokenMatcher
attr_reader :token_attribute
def initialize(token_attribute)
@token_attribute = token_attribute
end
def description
"have :#{token_attribute} as a secure token"
end
def failure_message
return if !@errors
"Expected #{@subject.class} to #{description} but the following " \
"errors were found: #{@errors.join(', ')}"
end
def failure_message_when_negated
return if !@errors
"Did not expect #{@subject.class} to have secure token " \
":#{token_attribute}"
end
def matches?(subject)
@subject = subject
@errors = run_checks
@errors.empty?
end
private
def run_checks
@errors = []
if !has_expected_instance_methods?
@errors << 'missing expected class and instance methods'
end
if !has_expected_db_column?
@errors << "missing correct column #{token_attribute}:string"
end
if !has_expected_db_index?
@errors << "missing unique index for #{table_and_column}"
end
@errors
end
def has_expected_instance_methods?
@subject.respond_to?(token_attribute.to_s) &&
@subject.respond_to?("#{token_attribute}=") &&
@subject.respond_to?("regenerate_#{token_attribute}") &&
@subject.class.respond_to?(:generate_unique_secure_token)
end
def has_expected_db_column?
matcher = HaveDbColumnMatcher.new(token_attribute).of_type(:string)
matcher.matches?(@subject)
end
def has_expected_db_index?
matcher = HaveDbIndexMatcher.new(token_attribute).unique(true)
matcher.matches?(@subject)
end
def table_and_column
"#{table_name}.#{token_attribute}"
end
def table_name
@subject.class.table_name
end
end
end
end
end

View File

@ -27,6 +27,7 @@ module Shoulda
end
def self.indent(string, width)
return if !string
indentation = ' ' * width
string.split(/[\n\r]/).map { |line| indentation + line }.join("\n")
end

View File

@ -17,6 +17,10 @@ module UnitTests
active_record_version >= 3.1
end
def active_record_supports_has_secure_token?
active_record_version >= 5.0
end
def active_record_supports_array_columns?
active_record_version > 4.2
end

View File

@ -0,0 +1,169 @@
require 'unit_spec_helper'
# rubocop:disable Metrics/BlockLength
describe Shoulda::Matchers::ActiveRecord::HaveSecureTokenMatcher,
type: :model do
if active_record_supports_has_secure_token?
describe '#description' do
it 'returns the message including the name of the default column' do
matcher = have_secure_token
expect(matcher.description).
to eq('have :token as a secure token')
end
it 'returns the message including the name of a provided column' do
matcher = have_secure_token(:special_token)
expect(matcher.description).
to eq('have :special_token as a secure token')
end
end
it 'matches when the subject configures has_secure_token with the db' do
create_table(:users) do |t|
t.string :token
t.index :token, unique: true
end
valid_model = define_model_class(:User) { has_secure_token }
expect(valid_model.new).to have_secure_token
end
it 'matches when the subject configures has_secure_token with the db for ' \
'a custom attribute' do
create_table(:users) do |t|
t.string :auth_token
t.index :auth_token, unique: true
end
valid_model = define_model_class(:User) { has_secure_token(:auth_token) }
expect(valid_model.new).to have_secure_token(:auth_token)
end
it 'does not match when missing an token index' do
create_table(:users) do |t|
t.string :token
end
invalid_model = define_model_class(:User) { has_secure_token }
expected_message =
'Expected User to have :token as a secure token but the following ' \
'errors were found: missing unique index for users.token'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token
expect { expect(invalid_model.new).to have_secure_token }.
to fail_with_message(expected_message)
end
end
it 'does not match when missing a token column' do
create_table(:users)
invalid_model = define_model_class(:User) { has_secure_token }
expected_message =
'Expected User to have :token as a secure token but the following ' \
'errors were found: missing expected class and instance methods, ' \
'missing correct column token:string, missing unique index for ' \
'users.token'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token
expect { expect(invalid_model.new).to have_secure_token }.
to fail_with_message(expected_message)
end
end
it 'does not match when when lacking has_secure_token' do
create_table(:users) do |t|
t.string :token
t.index :token
end
invalid_model = define_model_class(:User)
expected_message =
'Expected User to have :token as a secure token but the following ' \
'errors were found: missing expected class and instance methods, ' \
'missing unique index for users.token'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token
expect { expect(invalid_model.new).to have_secure_token }.
to fail_with_message(expected_message)
end
end
it 'does not match when missing an index for a custom attribute' do
create_table(:users) do |t|
t.string :auth_token
end
invalid_model = define_model_class(:User) do
has_secure_token(:auth_token)
end
expected_message =
'Expected User to have :auth_token as a secure token but the ' \
'following errors were found: missing unique index for ' \
'users.auth_token'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token(:auth_token)
expect { expect(invalid_model.new).to have_secure_token(:auth_token) }.
to fail_with_message(expected_message)
end
end
it 'does not match when missing a column for a custom attribute' do
create_table(:users)
invalid_model = define_model_class(:User) do
has_secure_token(:auth_token)
end
expected_message =
'Expected User to have :auth_token as a secure token but the ' \
'following errors were found: missing expected class and instance ' \
'methods, missing correct column auth_token:string, missing unique ' \
'index for users.auth_token'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token(:auth_token)
expect { expect(invalid_model.new).to have_secure_token(:auth_token) }.
to fail_with_message(expected_message)
end
end
it 'does not match when when lacking has_secure_token for the attribute' do
create_table(:users) do |t|
t.string :auth_token
t.index :auth_token, unique: true
end
invalid_model = define_model_class(:User)
expected_message =
'Expected User to have :auth_token as a secure token but the ' \
'following errors were found: missing expected class and instance ' \
'methods'
aggregate_failures do
expect(invalid_model.new).not_to have_secure_token(:auth_token)
expect { expect(invalid_model.new).to have_secure_token(:auth_token) }.
to fail_with_message(expected_message)
end
end
it 'fails with the appropriate message when negated' do
create_table(:users) do |t|
t.string :token
t.index :token, unique: true
end
valid_model = define_model_class(:User) { has_secure_token }
expect { expect(valid_model.new).not_to have_secure_token }.
to fail_with_message('Did not expect User to have secure token :token')
end
end
end