forked from thoughtbot/shoulda-matchers
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
286 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
lib/shoulda/matchers/active_record/have_secure_token_matcher.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
spec/unit/shoulda/matchers/active_record/have_secure_token_matcher_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |