data-anonymization 0.8.0 → 0.8.5
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 +5 -5
- data/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/.travis.yml +4 -3
- data/README.md +24 -8
- data/commands.txt +4 -0
- data/data-anonymization.gemspec +7 -7
- data/examples/blacklist_dsl.rb +3 -3
- data/examples/mongodb_blacklist_dsl.rb +5 -5
- data/examples/mongodb_whitelist_dsl.rb +7 -7
- data/examples/whitelist_dsl.rb +4 -4
- data/examples/whitelist_dsl_threads.rb +66 -0
- data/lib/core/field.rb +1 -1
- data/lib/core/table_errors.rb +1 -1
- data/lib/data-anonymization.rb +22 -22
- data/lib/strategy/base.rb +41 -1
- data/lib/strategy/blacklist.rb +3 -2
- data/lib/strategy/field/datetime/anonymize_time.rb +3 -3
- data/lib/strategy/field/default_anon.rb +1 -0
- data/lib/strategy/field/number/random_big_decimal_delta.rb +1 -1
- data/lib/strategy/field/string/random_url.rb +1 -1
- data/lib/strategy/field/string/select_from_database.rb +14 -7
- data/lib/strategy/strategies.rb +1 -1
- data/lib/thor/helpers/mongodb_dsl_generator.rb +2 -3
- data/lib/utils/database.rb +1 -1
- data/lib/utils/random_int.rb +2 -2
- data/lib/utils/template_helper.rb +4 -2
- data/lib/version.rb +1 -1
- data/spec/acceptance/mongodb_blacklist_spec.rb +39 -39
- data/spec/acceptance/mongodb_whitelist_spec.rb +45 -45
- data/spec/acceptance/rdbms_whitelist_spec.rb +1 -1
- data/spec/acceptance/rdbms_whitelist_with_primary_key_spec.rb +8 -8
- data/spec/core/fields_missing_strategy_spec.rb +15 -15
- data/spec/strategy/field/contact/random_address_spec.rb +2 -2
- data/spec/strategy/field/default_anon_spec.rb +3 -3
- data/spec/strategy/field/number/random_big_decimal_delta_spec.rb +1 -1
- data/spec/strategy/field/random_boolean_spec.rb +1 -1
- data/spec/strategy/field/whitelist_spec.rb +1 -1
- data/spec/strategy/mongodb/anonymize_field_spec.rb +11 -11
- data/spec/utils/database_spec.rb +4 -4
- data/spec/utils/template_helper_spec.rb +6 -6
- metadata +19 -18
data/lib/strategy/blacklist.rb
CHANGED
@@ -3,15 +3,16 @@ module DataAnon
|
|
3
3
|
class Blacklist < DataAnon::Strategy::Base
|
4
4
|
|
5
5
|
def process_record index, record
|
6
|
+
updates = {}
|
6
7
|
@fields.each do |field, strategy|
|
7
8
|
database_field_name = record.attributes.select { |k,v| k == field }.keys[0]
|
8
9
|
field_value = record.attributes[database_field_name]
|
9
10
|
unless field_value.nil? || is_primary_key?(database_field_name)
|
10
11
|
field = DataAnon::Core::Field.new(database_field_name, field_value, index, record, @name)
|
11
|
-
|
12
|
+
updates[database_field_name] = strategy.anonymize(field)
|
12
13
|
end
|
13
14
|
end
|
14
|
-
record.
|
15
|
+
record.update_columns(updates) if updates.any?
|
15
16
|
end
|
16
17
|
|
17
18
|
end
|
@@ -58,8 +58,8 @@ module DataAnon
|
|
58
58
|
month = @anonymize_month? DataAnon::Utils::RandomInt.generate(1,12) : original_time.month
|
59
59
|
days_in_month = Time.new(year,month,1,1,1,1).end_of_month.day
|
60
60
|
day = @anonymize_day? DataAnon::Utils::RandomInt.generate(1,days_in_month) : original_time.day
|
61
|
-
hour = @anonymize_hour? DataAnon::Utils::RandomInt.generate(
|
62
|
-
min = @anonymize_min? DataAnon::Utils::RandomInt.generate(
|
61
|
+
hour = @anonymize_hour? DataAnon::Utils::RandomInt.generate(0,23) : original_time.hour
|
62
|
+
min = @anonymize_min? DataAnon::Utils::RandomInt.generate(0,59) : original_time.min
|
63
63
|
sec = original_time.sec
|
64
64
|
|
65
65
|
create_object(year, month, day, hour, min, sec)
|
@@ -74,4 +74,4 @@ module DataAnon
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
77
|
-
end
|
77
|
+
end
|
@@ -5,6 +5,7 @@ module DataAnon
|
|
5
5
|
class DefaultAnon
|
6
6
|
|
7
7
|
DEFAULT_STRATEGIES = {:string => FieldStrategy::RandomString.new,
|
8
|
+
:integer => FieldStrategy::RandomIntegerDelta.new(5),
|
8
9
|
:fixnum => FieldStrategy::RandomIntegerDelta.new(5),
|
9
10
|
:bignum => FieldStrategy::RandomIntegerDelta.new(5000),
|
10
11
|
:float => FieldStrategy::RandomFloatDelta.new(5.0),
|
@@ -16,7 +16,7 @@ module DataAnon
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def anonymize field
|
19
|
-
return BigDecimal
|
19
|
+
return BigDecimal("#{field.value + DataAnon::Utils::RandomFloat.generate(-@delta, +@delta)}")
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
@@ -12,16 +12,23 @@ module DataAnon
|
|
12
12
|
include Utils::Logging
|
13
13
|
|
14
14
|
def initialize table_name, field_name, connection_spec
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
logger.debug "For field strategy #{table_name}:#{field_name} using values #{@values} "
|
19
|
-
|
15
|
+
@table_name = table_name
|
16
|
+
@field_name = field_name
|
17
|
+
@connection_spec = connection_spec
|
20
18
|
end
|
21
19
|
|
22
|
-
|
23
|
-
|
20
|
+
def anonymize field
|
21
|
+
@values ||= begin
|
22
|
+
DataAnon::Utils::SourceDatabase.establish_connection @connection_spec
|
23
|
+
source = Utils::SourceTable.create @table_name, []
|
24
|
+
values = source.select(@field_name).distinct.collect { |record| record[@field_name]}
|
25
|
+
logger.debug "For field strategy #{@table_name}:#{@field_name} using values #{values} "
|
26
|
+
values
|
27
|
+
end
|
24
28
|
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
data/lib/strategy/strategies.rb
CHANGED
@@ -9,5 +9,5 @@ begin
|
|
9
9
|
require 'strategy/mongodb/whitelist'
|
10
10
|
require 'strategy/mongodb/blacklist'
|
11
11
|
rescue LoadError
|
12
|
-
|
12
|
+
'Ignoring the mongodb specific libraries if monog driver is not specified in gem'
|
13
13
|
end
|
@@ -18,13 +18,13 @@ module DataAnon
|
|
18
18
|
|
19
19
|
def generate
|
20
20
|
|
21
|
-
db = Mongo::
|
21
|
+
db = Mongo::Client.new(@mongodb_uri, :database => @configuration_hash[:database])
|
22
22
|
collections = db.collections
|
23
23
|
collections.each do |collection|
|
24
24
|
unless collection.name.start_with?('system.')
|
25
25
|
depth = 2
|
26
26
|
@output << "\tcollection '#{collection.name}' do"
|
27
|
-
document = collection.
|
27
|
+
document = collection.find({}).first
|
28
28
|
process_document(depth, document)
|
29
29
|
@output << "\tend\n"
|
30
30
|
end
|
@@ -63,4 +63,3 @@ module DataAnon
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
data/lib/utils/database.rb
CHANGED
@@ -25,7 +25,7 @@ module DataAnon
|
|
25
25
|
|
26
26
|
def self.create_table database, table_name, primary_keys = []
|
27
27
|
klass_name = table_name.to_s.downcase.capitalize
|
28
|
-
return database.const_get
|
28
|
+
return database.const_get(klass_name, false) if database.const_defined?(klass_name, false)
|
29
29
|
database.const_set(klass_name, Class.new(database) do
|
30
30
|
self.table_name = table_name
|
31
31
|
self.primary_keys = primary_keys if primary_keys.length > 1
|
data/lib/utils/random_int.rb
CHANGED
@@ -5,9 +5,11 @@ module DataAnon
|
|
5
5
|
def self.source_connection_specs_rdbms config_hash
|
6
6
|
|
7
7
|
config_hash.keys.reject{|key| config_hash[key].nil? }.collect { |key|
|
8
|
-
if ((config_hash[key].class.to_s.downcase ==
|
8
|
+
if ((config_hash[key].class.to_s.downcase == 'string'))
|
9
9
|
":#{key} => '#{config_hash[key]}'"
|
10
|
-
elsif ((config_hash[key].class.to_s.downcase ==
|
10
|
+
elsif ((config_hash[key].class.to_s.downcase == 'integer'))
|
11
|
+
":#{key} => #{config_hash[key]}"
|
12
|
+
elsif ((config_hash[key].class.to_s.downcase == 'fixnum'))
|
11
13
|
":#{key} => #{config_hash[key]}"
|
12
14
|
end
|
13
15
|
}.join ', '
|
data/lib/version.rb
CHANGED
@@ -1,56 +1,56 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'mongo'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'End 2 End MongoDB Blacklist Acceptance Test' do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
Mongo::Client.new(
|
7
|
+
Mongo::Client.new('mongodb://localhost/test').database().drop()
|
8
8
|
users = [
|
9
9
|
{
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
'_id' => 1,
|
11
|
+
'USER_ID' => 'sunitparekh',
|
12
|
+
'date_of_birth' => Time.new(2012, 7, 14, 13, 1, 0),
|
13
|
+
'email' => 'parekh-sunit@mailinator.com',
|
14
|
+
'password' => 'TfqIK8Pd8GlbMDFZCX4l/5EtnOkfLCeynOL85tJQuxum&382knaflk@@',
|
15
|
+
'failed_attempts' => 0,
|
16
|
+
'first_name' => 'Sunit',
|
17
|
+
'last_name' => 'Parekh',
|
18
|
+
'password_reset_answer' => 'manza',
|
19
|
+
'password_reset_question' => 'My new car modal?',
|
20
|
+
'updated_at' => Time.new(2012, 8, 15, 13, 1, 0),
|
21
|
+
'alternate_emails' => ['abc@test.com', 'abc2@test.com']
|
22
22
|
|
23
23
|
},
|
24
24
|
{
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
25
|
+
'_id' => 2,
|
26
|
+
'USER_ID' => 'anandagrawal',
|
27
|
+
'date_of_birth' => Time.new(2011, 8, 11, 13, 1, 0),
|
28
|
+
'email' => 'anand-agrawal@mailinator.com',
|
29
|
+
'password' => 'Tz548O0RWusldVAWkwqfzO3jK/X4l/5EtnOkfLCeynOL85tJQuxum',
|
30
|
+
'failed_attempts' => 0,
|
31
|
+
'first_name' => 'Anand',
|
32
|
+
'last_name' => 'Agrawal',
|
33
|
+
'password_reset_answer' => 'android',
|
34
|
+
'password_reset_question' => 'My phone?',
|
35
|
+
'updated_at' => Time.new(2012, 2, 11, 13, 1, 0),
|
36
|
+
'alternate_emails' => ['abc@test.com', 'abc2@test.com']
|
37
37
|
}
|
38
38
|
]
|
39
|
-
users_coll = Mongo::Client.new(
|
39
|
+
users_coll = Mongo::Client.new('mongodb://localhost/test').database().collection('users')
|
40
40
|
users.each { |p| users_coll.insert_one p }
|
41
41
|
end
|
42
42
|
|
43
|
-
it
|
43
|
+
it 'should anonymize plans collection' do
|
44
44
|
|
45
45
|
database 'test' do
|
46
46
|
strategy DataAnon::Strategy::MongoDB::Blacklist
|
47
|
-
source_db :mongodb_uri =>
|
47
|
+
source_db :mongodb_uri => 'mongodb://localhost/test', :database => 'test'
|
48
48
|
|
49
49
|
collection 'users' do
|
50
50
|
anonymize('date_of_birth').using FieldStrategy::TimeDelta.new(5,30)
|
51
51
|
anonymize('USER_ID').using FieldStrategy::StringTemplate.new('user-#{row_number}')
|
52
52
|
anonymize('email').using FieldStrategy::RandomMailinatorEmail.new
|
53
|
-
anonymize('password') { |field|
|
53
|
+
anonymize('password') { |field| 'password'}
|
54
54
|
anonymize('first_name').using FieldStrategy::RandomFirstName.new
|
55
55
|
anonymize('last_name').using FieldStrategy::RandomLastName.new
|
56
56
|
anonymize('alternate_emails').using FieldStrategy::AnonymizeArray.new(FieldStrategy::RandomMailinatorEmail.new)
|
@@ -58,20 +58,20 @@ describe "End 2 End MongoDB Blacklist Acceptance Test" do
|
|
58
58
|
|
59
59
|
end
|
60
60
|
|
61
|
-
users_coll = Mongo::Client.new(
|
61
|
+
users_coll = Mongo::Client.new('mongodb://localhost/test').database().collection('users')
|
62
62
|
users_coll.find.count.to_int.should be 2
|
63
63
|
user = users_coll.find({'_id' => 1}).to_a[0]
|
64
64
|
|
65
65
|
user['_id'].should == 1
|
66
|
-
user['USER_ID'].should ==
|
66
|
+
user['USER_ID'].should == 'user-1'
|
67
67
|
user['date_of_birth'].to_i.should_not == Time.new(2012, 7, 14, 13, 1, 0).to_i
|
68
|
-
user['email'].should_not ==
|
69
|
-
user['password'].should ==
|
68
|
+
user['email'].should_not == 'parekh-sunit@mailinator.com'
|
69
|
+
user['password'].should == 'password'
|
70
70
|
user['failed_attempts'].should == 0
|
71
|
-
user['first_name'].should_not be
|
72
|
-
user['last_name'].should_not be
|
73
|
-
user['password_reset_answer'].should ==
|
74
|
-
user['password_reset_question'].should ==
|
71
|
+
user['first_name'].should_not be 'Sunit'
|
72
|
+
user['last_name'].should_not be 'Parekh'
|
73
|
+
user['password_reset_answer'].should == 'manza'
|
74
|
+
user['password_reset_question'].should == 'My new car modal?'
|
75
75
|
user['updated_at'].to_i.should == Time.new(2012, 8, 15, 13, 1, 0).to_i
|
76
76
|
user['alternate_emails'].length.should == 2
|
77
77
|
user['alternate_emails'][0].should_not == 'abc@test.com'
|
@@ -1,65 +1,65 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'mongo'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'End 2 End MongoDB Whitelist Acceptance Test' do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
Mongo::Client.new(
|
8
|
-
Mongo::Client.new(
|
7
|
+
Mongo::Client.new('mongodb://localhost/test').database.drop
|
8
|
+
Mongo::Client.new('mongodb://localhost/dest').database.drop
|
9
9
|
plans = [
|
10
10
|
{
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
'_id' => 1,
|
12
|
+
'name' => 'Free',
|
13
|
+
'nick_names' => ['Name1', 'Name2'],
|
14
|
+
'features' => [
|
15
15
|
{
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
'max_storage' => 21474836480,
|
17
|
+
'type' => 'AmazonS3',
|
18
|
+
'users' => {'max' => 1, 'additional' => false}
|
19
19
|
},
|
20
20
|
{
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
'max_storage' => 21474836480,
|
22
|
+
'type' => 'DropBox',
|
23
|
+
'users' => {'max' => 1, 'additional' => false}
|
24
24
|
}
|
25
25
|
],
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
'term' => 'month',
|
27
|
+
'public_sharing' => false,
|
28
|
+
'photo_sharing' => true,
|
29
|
+
'created_at' => Time.new(2012, 6, 21, 13, 30, 0)
|
30
30
|
},
|
31
31
|
{
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
'_id' => 2,
|
33
|
+
'name' => 'Team',
|
34
|
+
'plan_aliases' => ['Business', 'Paid'],
|
35
|
+
'features' => [
|
36
36
|
{
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
'max_storage' => 53687091200,
|
38
|
+
'type' => 'AmazonS3',
|
39
|
+
'users' => {'max' => 5, 'additional' => true}
|
40
40
|
},
|
41
41
|
{
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
'max_storage' => 53687091200,
|
43
|
+
'type' => 'DropBox',
|
44
|
+
'users' => {'max' => 5, 'additional' => true}
|
45
45
|
}
|
46
46
|
],
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
'term' => 'month',
|
48
|
+
'public_sharing' => true,
|
49
|
+
'photo_sharing' => true,
|
50
|
+
'created_at' => Time.new(2012, 8, 11, 13, 1, 0)
|
51
51
|
}
|
52
52
|
]
|
53
|
-
plans_coll = Mongo::Client.new(
|
53
|
+
plans_coll = Mongo::Client.new('mongodb://localhost/test').database.collection('plans')
|
54
54
|
plans.each { |p| plans_coll.insert_one p }
|
55
55
|
end
|
56
56
|
|
57
|
-
it
|
57
|
+
it 'should anonymize plans collection' do
|
58
58
|
|
59
59
|
database 'dest' do
|
60
60
|
strategy DataAnon::Strategy::MongoDB::Whitelist
|
61
|
-
source_db :mongodb_uri =>
|
62
|
-
destination_db :mongodb_uri =>
|
61
|
+
source_db :mongodb_uri => 'mongodb://localhost/test', :database => 'test'
|
62
|
+
destination_db :mongodb_uri => 'mongodb://localhost/dest', :database => 'dest'
|
63
63
|
|
64
64
|
collection 'plans' do
|
65
65
|
whitelist '_id', 'name', 'term', 'created_at'
|
@@ -78,15 +78,15 @@ describe "End 2 End MongoDB Whitelist Acceptance Test" do
|
|
78
78
|
|
79
79
|
end
|
80
80
|
|
81
|
-
plans_coll = Mongo::Client.new(
|
81
|
+
plans_coll = Mongo::Client.new('mongodb://localhost/dest').database.collection('plans')
|
82
82
|
plans_coll.find.count.to_int.should be 2
|
83
83
|
plan = plans_coll.find({ '_id' => 1}).to_a[0]
|
84
84
|
|
85
85
|
plan['_id'].should == 1
|
86
|
-
plan['name'].should ==
|
87
|
-
plan['nick_names'][0].should_not ==
|
88
|
-
plan['nick_names'][1].should_not ==
|
89
|
-
plan['term'].should ==
|
86
|
+
plan['name'].should == 'Free'
|
87
|
+
plan['nick_names'][0].should_not == 'Name1'
|
88
|
+
plan['nick_names'][1].should_not == 'Name2'
|
89
|
+
plan['term'].should == 'month'
|
90
90
|
plan['created_at'].should == Time.new(2012, 6, 21, 13, 30, 0)
|
91
91
|
plan['plan_aliases'].should be_nil
|
92
92
|
[true,false].should include(plan['public_sharing'])
|
@@ -94,14 +94,14 @@ describe "End 2 End MongoDB Whitelist Acceptance Test" do
|
|
94
94
|
plan['features'].length.should == 2
|
95
95
|
feature1 = plan['features'][0]
|
96
96
|
[10737418240, 21474836480, 53687091200].should include(feature1['max_storage'])
|
97
|
-
feature1['type'].should ==
|
98
|
-
feature1['users']['max'].should be_kind_of(
|
97
|
+
feature1['type'].should == 'AmazonS3'
|
98
|
+
feature1['users']['max'].should be_kind_of(Integer)
|
99
99
|
[true,false].should include(feature1['users']['additional'])
|
100
100
|
|
101
101
|
|
102
102
|
plan = plans_coll.find({ '_id' => 2}).to_a[0]
|
103
103
|
plan['plan_aliases'].length.should == 2
|
104
|
-
[
|
105
|
-
[
|
104
|
+
['Free', 'Team', 'Business', 'Paid'].should include(plan['plan_aliases'][0])
|
105
|
+
['Free', 'Team', 'Business', 'Paid'].should include(plan['plan_aliases'][1])
|
106
106
|
end
|
107
107
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe 'End 2 End RDBMS Whitelist Acceptance Test using SQLite database' do
|
4
4
|
|
5
5
|
source_connection_spec = {:adapter => 'sqlite3', :database => 'tmp/customer.sqlite'}
|
6
6
|
dest_connection_spec = {:adapter => 'sqlite3', :database => 'tmp/customer-dest.sqlite'}
|
@@ -13,9 +13,9 @@ describe "End 2 End RDBMS Whitelist Acceptance Test using SQLite database" do
|
|
13
13
|
CustomerSample.create_schema dest_connection_spec
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
16
|
+
it 'should anonymize customer table record ' do
|
17
17
|
|
18
|
-
database
|
18
|
+
database 'Customer' do
|
19
19
|
strategy DataAnon::Strategy::Whitelist
|
20
20
|
source_db source_connection_spec
|
21
21
|
destination_db dest_connection_spec
|
@@ -37,14 +37,14 @@ describe "End 2 End RDBMS Whitelist Acceptance Test using SQLite database" do
|
|
37
37
|
|
38
38
|
DataAnon::Utils::DestinationDatabase.establish_connection dest_connection_spec
|
39
39
|
dest_table = DataAnon::Utils::DestinationTable.create 'customers'
|
40
|
-
new_rec = dest_table.where(
|
41
|
-
new_rec.first_name.should_not be(
|
42
|
-
new_rec.last_name.should_not be(
|
40
|
+
new_rec = dest_table.where('cust_id' => CustomerSample::SAMPLE_DATA[0][:cust_id]).first
|
41
|
+
new_rec.first_name.should_not be('Sunit')
|
42
|
+
new_rec.last_name.should_not be('Parekh')
|
43
43
|
new_rec.birth_date.should_not be(Date.new(1977,7,8))
|
44
44
|
new_rec.address.should == 'F 501 Shanti Nagar'
|
45
45
|
['Gujrat','Karnataka'].should include(new_rec.state)
|
46
46
|
new_rec.zipcode.should == '411048'
|
47
|
-
new_rec.phone.should_not be
|
47
|
+
new_rec.phone.should_not be '9923700662'
|
48
48
|
new_rec.email.should == 'test+1@gmail.com'
|
49
49
|
[true,false].should include(new_rec.terms_n_condition)
|
50
50
|
new_rec.age.should be_between(0,100)
|