attr_masker 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +15 -0
- data/.gitignore +5 -0
- data/.hound.yml +3 -0
- data/.rspec +1 -0
- data/.rubocop.yml +1076 -0
- data/.travis.yml +32 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.adoc +153 -0
- data/Rakefile +32 -0
- data/attr_masker.gemspec +34 -0
- data/config.ru +7 -0
- data/gemfiles/Rails-4.0.gemfile +5 -0
- data/gemfiles/Rails-4.1.gemfile +5 -0
- data/gemfiles/Rails-4.2.gemfile +5 -0
- data/gemfiles/Rails-5.0.gemfile +5 -0
- data/gemfiles/Rails-5.1.gemfile +5 -0
- data/gemfiles/Rails-head.gemfile +6 -0
- data/lib/attr_masker/error.rb +6 -0
- data/lib/attr_masker/maskers/replacing.rb +30 -0
- data/lib/attr_masker/maskers/simple.rb +12 -0
- data/lib/attr_masker/performer.rb +67 -0
- data/lib/attr_masker/railtie.rb +17 -0
- data/lib/attr_masker/version.rb +21 -0
- data/lib/attr_masker.rb +227 -0
- data/lib/tasks/db.rake +21 -0
- data/spec/dummy/config/database.yml +3 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/schema.rb +8 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/features_spec.rb +203 -0
- data/spec/maskers/replacing_spec.rb +48 -0
- data/spec/maskers/simple_spec.rb +14 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/0_combustion.rb +5 -0
- data/spec/support/db_cleaner.rb +15 -0
- data/spec/support/matchers.rb +2 -0
- data/spec/support/rake.rb +6 -0
- data/spec/support/silence_stdout.rb +8 -0
- metadata +229 -0
data/lib/attr_masker.rb
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# Adds attr_accessors that mask an object's attributes
|
5
|
+
module AttrMasker
|
6
|
+
autoload :Version, "attr_masker/version"
|
7
|
+
|
8
|
+
autoload :Error, "attr_masker/error"
|
9
|
+
autoload :Performer, "attr_masker/performer"
|
10
|
+
|
11
|
+
module Maskers
|
12
|
+
autoload :Replacing, "attr_masker/maskers/replacing"
|
13
|
+
autoload :SIMPLE, "attr_masker/maskers/simple"
|
14
|
+
end
|
15
|
+
|
16
|
+
require "attr_masker/railtie" if defined?(Rails)
|
17
|
+
def self.extended(base) # :nodoc:
|
18
|
+
base.class_eval do
|
19
|
+
|
20
|
+
# Only include the dangerous instance methods during the Rake task!
|
21
|
+
include InstanceMethods
|
22
|
+
attr_writer :attr_masker_options
|
23
|
+
@attr_masker_options, @masker_attributes = {}, {}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generates attr_accessors that mask attributes transparently
|
28
|
+
#
|
29
|
+
# Options (any other options you specify are passed to the masker's mask
|
30
|
+
# methods)
|
31
|
+
#
|
32
|
+
# :marshal => If set to true, attributes will be marshaled as well as masker. This is useful if you're planning
|
33
|
+
# on masking something other than a string. Defaults to false unless you're using it with ActiveRecord
|
34
|
+
# or DataMapper.
|
35
|
+
#
|
36
|
+
# :marshaler => The object to use for marshaling. Defaults to Marshal.
|
37
|
+
#
|
38
|
+
# :dump_method => The dump method name to call on the <tt>:marshaler</tt> object to. Defaults to 'dump'.
|
39
|
+
#
|
40
|
+
# :load_method => The load method name to call on the <tt>:marshaler</tt> object. Defaults to 'load'.
|
41
|
+
#
|
42
|
+
# :masker => The object to use for masking. It must respond to +#mask+. Defaults to AttrMasker::Maskers::Simple.
|
43
|
+
#
|
44
|
+
# :if => Attributes are only masker if this option evaluates to true. If you pass a symbol representing an instance
|
45
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
46
|
+
# Defaults to true.
|
47
|
+
#
|
48
|
+
# :unless => Attributes are only masker if this option evaluates to false. If you pass a symbol representing an instance
|
49
|
+
# method then the result of the method will be evaluated. Any objects that respond to <tt>:call</tt> are evaluated as well.
|
50
|
+
# Defaults to false.
|
51
|
+
#
|
52
|
+
# You can specify your own default options
|
53
|
+
#
|
54
|
+
# class User
|
55
|
+
# # now all attributes will be encoded and marshaled by default
|
56
|
+
# attr_masker_options.merge!(:marshal => true, :some_other_option => true)
|
57
|
+
# attr_masker :configuration
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
#
|
61
|
+
# Example
|
62
|
+
#
|
63
|
+
# class User
|
64
|
+
# attr_masker :email, :credit_card
|
65
|
+
# attr_masker :configuration, :marshal => true
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# @user = User.new
|
69
|
+
# @user.masker_email # nil
|
70
|
+
# @user.email? # false
|
71
|
+
# @user.email = 'test@example.com'
|
72
|
+
# @user.email? # true
|
73
|
+
# @user.masker_email # returns the masker version of 'test@example.com'
|
74
|
+
#
|
75
|
+
# @user.configuration = { :time_zone => 'UTC' }
|
76
|
+
# @user.masker_configuration # returns the masker version of configuration
|
77
|
+
#
|
78
|
+
# See README for more examples
|
79
|
+
def attr_masker(*attributes)
|
80
|
+
options = {
|
81
|
+
:if => true,
|
82
|
+
:unless => false,
|
83
|
+
:column_name => nil,
|
84
|
+
:marshal => false,
|
85
|
+
:marshaler => Marshal,
|
86
|
+
:dump_method => "dump",
|
87
|
+
:load_method => "load",
|
88
|
+
:masker => AttrMasker::Maskers::SIMPLE,
|
89
|
+
}.merge!(attr_masker_options).merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
|
90
|
+
|
91
|
+
attributes.each do |attribute|
|
92
|
+
masker_attributes[attribute.to_sym] = options.merge(attribute: attribute.to_sym)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Default options to use with calls to <tt>attr_masker</tt>
|
97
|
+
# XXX:Keep
|
98
|
+
#
|
99
|
+
# It will inherit existing options from its superclass
|
100
|
+
def attr_masker_options
|
101
|
+
@attr_masker_options ||= superclass.attr_masker_options.dup
|
102
|
+
end
|
103
|
+
|
104
|
+
# Checks if an attribute is configured with <tt>attr_masker</tt>
|
105
|
+
# XXX:Keep
|
106
|
+
#
|
107
|
+
# Example
|
108
|
+
#
|
109
|
+
# class User
|
110
|
+
# attr_accessor :name
|
111
|
+
# attr_masker :email
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# User.attr_masker?(:name) # false
|
115
|
+
# User.attr_masker?(:email) # true
|
116
|
+
def attr_masker?(attribute)
|
117
|
+
masker_attributes.has_key?(attribute.to_sym)
|
118
|
+
end
|
119
|
+
|
120
|
+
# masks a value for the attribute specified
|
121
|
+
# XXX:modify
|
122
|
+
#
|
123
|
+
# Example
|
124
|
+
#
|
125
|
+
# class User
|
126
|
+
# attr_masker :email
|
127
|
+
# end
|
128
|
+
#
|
129
|
+
# masker_email = User.mask(:email, 'test@example.com')
|
130
|
+
def mask(attribute, value, options = {})
|
131
|
+
options = masker_attributes[attribute.to_sym].merge(options)
|
132
|
+
# if options[:if] && !options[:unless] && !value.nil? && !(value.is_a?(String) && value.empty?)
|
133
|
+
if options[:if] && !options[:unless]
|
134
|
+
value = options[:marshal] ? options[:marshaler].send(options[:dump_method], value) : value.to_s
|
135
|
+
masker_value = options[:masker].call(options.merge!(value: value))
|
136
|
+
masker_value
|
137
|
+
else
|
138
|
+
value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Contains a hash of masker attributes with virtual attribute names as keys
|
143
|
+
# and their corresponding options as values
|
144
|
+
# XXX:Keep
|
145
|
+
#
|
146
|
+
# Example
|
147
|
+
#
|
148
|
+
# class User
|
149
|
+
# attr_masker :email
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# User.masker_attributes # { :email => { :attribute => 'masker_email' } }
|
153
|
+
def masker_attributes
|
154
|
+
@masker_attributes ||= superclass.masker_attributes.dup
|
155
|
+
end
|
156
|
+
|
157
|
+
# Forwards calls to :mask_#{attribute} to the corresponding mask method
|
158
|
+
# if attribute was configured with attr_masker
|
159
|
+
#
|
160
|
+
# Example
|
161
|
+
#
|
162
|
+
# class User
|
163
|
+
# attr_masker :email
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# User.mask_email('SOME_masker_EMAIL_STRING')
|
167
|
+
def method_missing(method, *arguments, &block)
|
168
|
+
if method.to_s =~ /^mask_(.+)$/ && attr_masker?($1)
|
169
|
+
send(:mask, $1, *arguments)
|
170
|
+
else
|
171
|
+
super
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
module InstanceMethods
|
176
|
+
|
177
|
+
# masks a value for the attribute specified using options evaluated in the current object's scope
|
178
|
+
#
|
179
|
+
# Example
|
180
|
+
#
|
181
|
+
# class User
|
182
|
+
# attr_accessor :secret_key
|
183
|
+
# attr_masker :email
|
184
|
+
#
|
185
|
+
# def initialize(secret_key)
|
186
|
+
# self.secret_key = secret_key
|
187
|
+
# end
|
188
|
+
# end
|
189
|
+
#
|
190
|
+
# @user = User.new('some-secret-key')
|
191
|
+
# @user.mask(:email, 'test@example.com')
|
192
|
+
def mask(attribute, value=nil)
|
193
|
+
value = self.send(attribute) if value.nil?
|
194
|
+
self.class.mask(attribute, value, evaluated_attr_masker_options_for(attribute))
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
# Returns attr_masker options evaluated in the current object's scope for the attribute specified
|
200
|
+
# XXX:Keep
|
201
|
+
def evaluated_attr_masker_options_for(attribute)
|
202
|
+
self.class.masker_attributes[attribute.to_sym].inject({}) do |hash, (option, value)|
|
203
|
+
if %i[if unless].include?(option)
|
204
|
+
hash.merge!(option => evaluate_attr_masker_option(value))
|
205
|
+
else
|
206
|
+
hash.merge!(option => value)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Evaluates symbol (method reference) or proc (responds to call) options
|
212
|
+
# XXX:Keep
|
213
|
+
#
|
214
|
+
# If the option is not a symbol or proc then the original option is returned
|
215
|
+
def evaluate_attr_masker_option(option)
|
216
|
+
if option.is_a?(Symbol) && respond_to?(option)
|
217
|
+
send(option)
|
218
|
+
elsif option.respond_to?(:call)
|
219
|
+
option.call(self)
|
220
|
+
else
|
221
|
+
option
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
Object.extend AttrMasker
|
data/lib/tasks/db.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# Hashrocket style looks better when describing task dependencies.
|
5
|
+
# rubocop:disable Style/HashSyntax
|
6
|
+
|
7
|
+
namespace :db do
|
8
|
+
desc "Mask every DB record according to rules set up in the respective " \
|
9
|
+
"ActiveRecord"
|
10
|
+
|
11
|
+
# If just:
|
12
|
+
# task :mask do ... end,
|
13
|
+
# then connection won't be established. Will need the '=> :environment'.
|
14
|
+
#
|
15
|
+
# URL:
|
16
|
+
# http://stackoverflow.com/questions/14163938/activerecordconnectionnotestablished-within-a-rake-task
|
17
|
+
#
|
18
|
+
task :mask => :environment do
|
19
|
+
AttrMasker::Performer::ActiveRecord.new.mask
|
20
|
+
end
|
21
|
+
end
|
File without changes
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# No point in using ApplicationRecord here.
|
5
|
+
# rubocop:disable Rails/ApplicationRecord
|
6
|
+
|
7
|
+
# No point in ensuring a trailing comma in multiline argument lists here.
|
8
|
+
# rubocop:disable Style/TrailingCommaInArguments
|
9
|
+
|
10
|
+
require "spec_helper"
|
11
|
+
|
12
|
+
RSpec.describe "Attr Masker gem", :suppress_progressbar do
|
13
|
+
before do
|
14
|
+
stub_const "User", Class.new(ActiveRecord::Base)
|
15
|
+
|
16
|
+
User.class_eval do
|
17
|
+
def jedi?
|
18
|
+
email.ends_with? "@jedi.example.test"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
allow(ActiveRecord::Base).to receive(:descendants).
|
23
|
+
and_return([ActiveRecord::SchemaMigration, User])
|
24
|
+
end
|
25
|
+
|
26
|
+
let!(:han) do
|
27
|
+
User.create!(
|
28
|
+
first_name: "Han",
|
29
|
+
last_name: "Solo",
|
30
|
+
email: "han@example.test",
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
let!(:luke) do
|
35
|
+
User.create!(
|
36
|
+
first_name: "Luke",
|
37
|
+
last_name: "Skywalker",
|
38
|
+
email: "luke@jedi.example.test",
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
example "Masking a single text attribute with default options" do
|
43
|
+
User.class_eval do
|
44
|
+
attr_masker :last_name
|
45
|
+
end
|
46
|
+
|
47
|
+
expect { run_rake_task }.not_to(change { User.count })
|
48
|
+
|
49
|
+
[han, luke].each do |record|
|
50
|
+
expect { record.reload }.to(
|
51
|
+
change { record.last_name }.to("(redacted)") &
|
52
|
+
preserve { record.first_name } &
|
53
|
+
preserve { record.email }
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
example "Specifying multiple attributes in an attr_masker declaration" do
|
59
|
+
User.class_eval do
|
60
|
+
attr_masker :first_name, :last_name
|
61
|
+
end
|
62
|
+
|
63
|
+
expect { run_rake_task }.not_to(change { User.count })
|
64
|
+
|
65
|
+
[han, luke].each do |record|
|
66
|
+
expect { record.reload }.to(
|
67
|
+
change { record.first_name }.to("(redacted)") &
|
68
|
+
change { record.last_name }.to("(redacted)") &
|
69
|
+
preserve { record.email }
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
example "Skipping some records when a symbol is passed to :if option" do
|
75
|
+
User.class_eval do
|
76
|
+
attr_masker :first_name, :last_name, if: :jedi?
|
77
|
+
end
|
78
|
+
|
79
|
+
expect { run_rake_task }.not_to(change { User.count })
|
80
|
+
|
81
|
+
expect { han.reload }.to(
|
82
|
+
preserve { han.first_name } &
|
83
|
+
preserve { han.last_name } &
|
84
|
+
preserve { han.email }
|
85
|
+
)
|
86
|
+
|
87
|
+
expect { luke.reload }.to(
|
88
|
+
change { luke.first_name }.to("(redacted)") &
|
89
|
+
change { luke.last_name }.to("(redacted)") &
|
90
|
+
preserve { luke.email }
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
example "Skipping some records when a lambda is passed to :if option" do
|
95
|
+
User.class_eval do
|
96
|
+
attr_masker :first_name, :last_name, if: ->(r) { r.jedi? }
|
97
|
+
end
|
98
|
+
|
99
|
+
expect { run_rake_task }.not_to(change { User.count })
|
100
|
+
|
101
|
+
expect { han.reload }.to(
|
102
|
+
preserve { han.first_name } &
|
103
|
+
preserve { han.last_name } &
|
104
|
+
preserve { han.email }
|
105
|
+
)
|
106
|
+
|
107
|
+
expect { luke.reload }.to(
|
108
|
+
change { luke.first_name }.to("(redacted)") &
|
109
|
+
change { luke.last_name }.to("(redacted)") &
|
110
|
+
preserve { luke.email }
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
example "Skipping some records when a symbol is passed to :unless option" do
|
115
|
+
User.class_eval do
|
116
|
+
attr_masker :first_name, :last_name, unless: :jedi?
|
117
|
+
end
|
118
|
+
|
119
|
+
expect { run_rake_task }.not_to(change { User.count })
|
120
|
+
|
121
|
+
expect { han.reload }.to(
|
122
|
+
change { han.first_name }.to("(redacted)") &
|
123
|
+
change { han.last_name }.to("(redacted)") &
|
124
|
+
preserve { han.email }
|
125
|
+
)
|
126
|
+
|
127
|
+
expect { luke.reload }.to(
|
128
|
+
preserve { luke.first_name } &
|
129
|
+
preserve { luke.last_name } &
|
130
|
+
preserve { luke.email }
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
example "Skipping some records when a lambda is passed to :unless option" do
|
135
|
+
User.class_eval do
|
136
|
+
attr_masker :first_name, :last_name, unless: ->(r) { r.jedi? }
|
137
|
+
end
|
138
|
+
|
139
|
+
expect { run_rake_task }.not_to(change { User.count })
|
140
|
+
|
141
|
+
expect { han.reload }.to(
|
142
|
+
change { han.first_name }.to("(redacted)") &
|
143
|
+
change { han.last_name }.to("(redacted)") &
|
144
|
+
preserve { han.email }
|
145
|
+
)
|
146
|
+
|
147
|
+
expect { luke.reload }.to(
|
148
|
+
preserve { luke.first_name } &
|
149
|
+
preserve { luke.last_name } &
|
150
|
+
preserve { luke.email }
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
example "Using a custom masker" do
|
155
|
+
reverse_masker = ->(value:, **_) do
|
156
|
+
value.reverse
|
157
|
+
end
|
158
|
+
|
159
|
+
upcase_masker = ->(value:, **_) do
|
160
|
+
value.upcase
|
161
|
+
end
|
162
|
+
|
163
|
+
User.class_eval do
|
164
|
+
attr_masker :first_name, masker: reverse_masker
|
165
|
+
attr_masker :last_name, masker: upcase_masker
|
166
|
+
end
|
167
|
+
|
168
|
+
expect { run_rake_task }.not_to(change { User.count })
|
169
|
+
|
170
|
+
expect { han.reload }.to(
|
171
|
+
change { han.first_name }.to("naH") &
|
172
|
+
change { han.last_name }.to("SOLO") &
|
173
|
+
preserve { han.email }
|
174
|
+
)
|
175
|
+
|
176
|
+
expect { luke.reload }.to(
|
177
|
+
change { luke.first_name }.to("ekuL") &
|
178
|
+
change { luke.last_name }.to("SKYWALKER") &
|
179
|
+
preserve { luke.email }
|
180
|
+
)
|
181
|
+
end
|
182
|
+
|
183
|
+
example "It is disabled in production environment" do
|
184
|
+
allow(Rails).to receive(:env) { "production".inquiry }
|
185
|
+
|
186
|
+
User.class_eval do
|
187
|
+
attr_masker :last_name
|
188
|
+
end
|
189
|
+
|
190
|
+
expect { run_rake_task }.to(
|
191
|
+
preserve { User.count } &
|
192
|
+
raise_exception(AttrMasker::Error)
|
193
|
+
)
|
194
|
+
|
195
|
+
[han, luke].each do |record|
|
196
|
+
expect { record.reload }.not_to(change { record })
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def run_rake_task
|
201
|
+
Rake::Task["db:mask"].execute
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# No point in using ApplicationRecord here.
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
RSpec.describe AttrMasker::Maskers::Replacing do
|
9
|
+
subject { described_class.new **options }
|
10
|
+
|
11
|
+
let(:address) { "1 Pedder Street, Hong Kong" }
|
12
|
+
|
13
|
+
shared_examples "AttrMasker::Maskers::Replacing examples" do
|
14
|
+
example { expect(subject.(value: address)).to eq(expected_masked_address) }
|
15
|
+
example { expect(subject.(value: Math::PI)).to eq(Math::PI) }
|
16
|
+
example { expect(subject.(value: nil)).to eq(nil) }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with default options" do
|
20
|
+
let(:options) { {} }
|
21
|
+
let(:expected_masked_address) { "**************************" }
|
22
|
+
include_examples "AttrMasker::Maskers::Replacing examples"
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with alphanum_only option set to true" do
|
26
|
+
let(:options) { { alphanum_only: true } }
|
27
|
+
let(:expected_masked_address) { "* ****** ******, **** ****" }
|
28
|
+
include_examples "AttrMasker::Maskers::Replacing examples"
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with a custom replacement string" do
|
32
|
+
let(:options) { { replacement: "X" } }
|
33
|
+
let(:expected_masked_address) { "XXXXXXXXXXXXXXXXXXXXXXXXXX" }
|
34
|
+
include_examples "AttrMasker::Maskers::Replacing examples"
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with an empty replacement string" do
|
38
|
+
let(:options) { { replacement: "" } }
|
39
|
+
let(:expected_masked_address) { "" }
|
40
|
+
include_examples "AttrMasker::Maskers::Replacing examples"
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with alphanum_only and replacement options combined" do
|
44
|
+
let(:options) { { alphanum_only: true, replacement: "X" } }
|
45
|
+
let(:expected_masked_address) { "X XXXXXX XXXXXX, XXXX XXXX" }
|
46
|
+
include_examples "AttrMasker::Maskers::Replacing examples"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
# No point in using ApplicationRecord here.
|
5
|
+
|
6
|
+
require "spec_helper"
|
7
|
+
|
8
|
+
RSpec.describe AttrMasker::Maskers::SIMPLE do
|
9
|
+
subject { described_class }
|
10
|
+
|
11
|
+
example { expect(subject.(value: "Solo")).to eq("(redacted)") }
|
12
|
+
example { expect(subject.(value: Math::PI)).to eq("(redacted)") }
|
13
|
+
example { expect(subject.(value: nil)).to eq("(redacted)") }
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# (c) 2017 Ribose Inc.
|
2
|
+
#
|
3
|
+
|
4
|
+
require "bundler"
|
5
|
+
Bundler.require :default, :development
|
6
|
+
|
7
|
+
Dir[File.expand_path "../support/**/*.rb", __FILE__].each { |f| require f }
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
# Enable flags like --only-failures and --next-failure
|
11
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
12
|
+
|
13
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
14
|
+
config.disable_monkey_patching!
|
15
|
+
|
16
|
+
config.expect_with :rspec do |c|
|
17
|
+
c.syntax = :expect
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require "rails/all"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "database_cleaner"
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.before(:suite) do
|
5
|
+
DatabaseCleaner.clean_with(:truncation)
|
6
|
+
|
7
|
+
DatabaseCleaner.strategy = :truncation
|
8
|
+
end
|
9
|
+
|
10
|
+
config.around(:each) do |example|
|
11
|
+
DatabaseCleaner.cleaning do
|
12
|
+
example.run
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|