Add matcher for has_secure_token
This commit is contained in:
parent
4b160bd19e
commit
bf38660507
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue