attr_masker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|