db_obfuscation 0.0.1
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/.gitignore +6 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +83 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/TODO +15 -0
- data/bin/console +25 -0
- data/bin/db_obfuscation +121 -0
- data/bin/obfuscation_test +54 -0
- data/cli/db_dump.rb +29 -0
- data/cli/migrator.rb +26 -0
- data/cli/seeder.rb +52 -0
- data/db_obfuscation.gemspec +25 -0
- data/features/bin/dump.feature +21 -0
- data/features/bin/obfuscation.feature +12 -0
- data/features/bin/test_database_tasks.feature +16 -0
- data/features/support.rb +1 -0
- data/lib/db_obfuscation.rb +50 -0
- data/lib/db_obfuscation/batch_formulator.rb +26 -0
- data/lib/db_obfuscation/config.rb +43 -0
- data/lib/db_obfuscation/database.rb +8 -0
- data/lib/db_obfuscation/environment.rb +14 -0
- data/lib/db_obfuscation/filtering.rb +56 -0
- data/lib/db_obfuscation/filtering/column.rb +40 -0
- data/lib/db_obfuscation/filtering/truncation.rb +18 -0
- data/lib/db_obfuscation/obfuscation_strategy.rb +22 -0
- data/lib/db_obfuscation/obfuscator.rb +65 -0
- data/lib/db_obfuscation/query_builder.rb +62 -0
- data/lib/db_obfuscation/truncation.rb +39 -0
- data/lib/db_obfuscation/util/trigger.rb +83 -0
- data/lib/db_obfuscation/version.rb +4 -0
- data/spec/cli/db_dump_spec.rb +33 -0
- data/spec/cli/migrator_spec.rb +59 -0
- data/spec/cli/seeder_spec.rb +33 -0
- data/spec/config/database.yml +5 -0
- data/spec/config/table_strategies/table_1.yml +3 -0
- data/spec/config/table_strategies/table_2.yml +4 -0
- data/spec/config/table_strategies/truncation_table_1.yml +3 -0
- data/spec/config/table_strategies/whitelisted_table_1.yml +3 -0
- data/spec/config/truncation_patterns.yml +2 -0
- data/spec/config/whitelisted_tables.yml +1 -0
- data/spec/db_obfuscation/batch_formulator_spec.rb +36 -0
- data/spec/db_obfuscation/config_spec.rb +60 -0
- data/spec/db_obfuscation/database_spec.rb +10 -0
- data/spec/db_obfuscation/filtering/column_spec.rb +82 -0
- data/spec/db_obfuscation/filtering/truncation_spec.rb +41 -0
- data/spec/db_obfuscation/filtering_spec.rb +39 -0
- data/spec/db_obfuscation/obfuscation_strategy_spec.rb +43 -0
- data/spec/db_obfuscation/obfuscator_spec.rb +150 -0
- data/spec/db_obfuscation/query_builder_spec.rb +259 -0
- data/spec/db_obfuscation/truncation_spec.rb +31 -0
- data/spec/db_obfuscation/util/trigger_spec.rb +126 -0
- data/spec/integration/obfuscation_spec.rb +69 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/test_db_setup/migrations/1_add_table_1.rb +18 -0
- data/spec/test_db_setup/migrations/2_add_table_2.rb +19 -0
- data/spec/test_db_setup/migrations/3_add_truncation_table_1.rb +14 -0
- data/spec/test_db_setup/migrations/4_add_whitelisted_table_1.rb +14 -0
- data/spec/test_db_setup/migrations/5_add_table_without_any_user_defined_obfuscation_strategies.rb +18 -0
- data/spec/test_db_setup/migrations/6_add_table_without_any_obfuscatable_columns.rb +15 -0
- data/spec/test_db_setup/migrations/7_add_audit_truncation_table.rb +13 -0
- data/spec/test_db_setup/seeds/audit_truncation_table.yml +7 -0
- data/spec/test_db_setup/seeds/table_1.yml +13 -0
- data/spec/test_db_setup/seeds/table_2.yml +15 -0
- data/spec/test_db_setup/seeds/table_without_any_obfuscatable_columns.yml +7 -0
- data/spec/test_db_setup/seeds/table_without_any_user_defined_obfuscation_strategies.yml +13 -0
- data/spec/test_db_setup/seeds/truncation_table_1.yml +9 -0
- data/spec/test_db_setup/seeds/whitelisted_table_1.yml +9 -0
- metadata +159 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/config'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
describe Config do
|
6
|
+
|
7
|
+
it 'looks for database configuration file' do
|
8
|
+
dir = DbObfuscation.config_dir
|
9
|
+
expect(described_class.config_path).to eq(dir)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'takes database configuration file from users' do
|
13
|
+
described_class.config_path = '/var'
|
14
|
+
expect(described_class.config_path).to eq(Pathname.new('/var'))
|
15
|
+
end
|
16
|
+
|
17
|
+
context '.db_config' do
|
18
|
+
before do
|
19
|
+
config_path = DbObfuscation.config_dir
|
20
|
+
described_class.config_path = config_path
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'loads the configuration file' do
|
24
|
+
db_config = { "database" => 'obfuscation_test' }
|
25
|
+
expect(described_class.db_config).to include db_config
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'provides database information from the configuration file' do
|
29
|
+
db_config = described_class.db_config
|
30
|
+
expect(db_config['username']).to eq('ccUser')
|
31
|
+
expect(db_config['database']).to eq('obfuscation_test')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context '.truncation_patterns' do
|
36
|
+
it 'returns collection of truncation patterns' do
|
37
|
+
patterns = described_class.truncation_patterns
|
38
|
+
expect(patterns).to include(:truncation_table_1)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context '.whitelisted_tables' do
|
43
|
+
it 'returns collection of tables names' do
|
44
|
+
whitelisted_tables = described_class.whitelisted_tables
|
45
|
+
expect(whitelisted_tables).to include('whitelisted_table_1')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context '.table_strategies' do
|
50
|
+
it 'returns all the obfuscation strategies for tables' do
|
51
|
+
strategies = described_class.table_strategies
|
52
|
+
phone_masking_technique = strategies['table_1']['field_1']
|
53
|
+
expect(phone_masking_technique).to eq(:first_name_strategy)
|
54
|
+
|
55
|
+
address_masking_technique = strategies['table_2']['date_field']
|
56
|
+
expect(address_masking_technique).to eq(:date_strategy)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/filtering/column'
|
3
|
+
|
4
|
+
module DbObfuscation::Filtering
|
5
|
+
describe Column do
|
6
|
+
context '.columns_type' do
|
7
|
+
it 'returns the column and types for the table' do
|
8
|
+
column_types = {
|
9
|
+
id: :integer,
|
10
|
+
field_1: :string,
|
11
|
+
field_2: :string,
|
12
|
+
date_field: :date,
|
13
|
+
created_at: :datetime,
|
14
|
+
updated_at: :datetime,
|
15
|
+
boolean_field: :boolean,
|
16
|
+
integer_field: :integer
|
17
|
+
}
|
18
|
+
|
19
|
+
expect(described_class.columns_type('table_1')).to eq(column_types)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '.type?' do
|
24
|
+
let(:expected_column_type) { [:string] }
|
25
|
+
let(:column_type) { :string }
|
26
|
+
|
27
|
+
it 'returns true when types matches' do
|
28
|
+
column_type = :string
|
29
|
+
expect(described_class.type?(expected_column_type, column_type)).to eq true
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'returns false when types does not match' do
|
33
|
+
column_type = :int
|
34
|
+
expect(described_class.type?(expected_column_type, column_type)).to eq false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.polymorphic?' do
|
39
|
+
it 'returns true when the column name is polymorphic association column' do
|
40
|
+
column_name = 'column_name_type'
|
41
|
+
expect(described_class.polymorphic?(column_name)).to eq true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns false when the column name is not polymorphic association column' do
|
45
|
+
column_name = 'simple_column_name'
|
46
|
+
expect(described_class.polymorphic?(column_name)).to eq false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '.ending_in_id?' do
|
51
|
+
it 'returns true if the column name ends in "id"' do
|
52
|
+
column_name = 'simple_column_name_id'
|
53
|
+
expect(described_class.ending_in_id?(column_name)).to eq true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns false if the column name does not ends in "id"' do
|
57
|
+
column_name = 'simple_column_name'
|
58
|
+
expect(described_class.ending_in_id?(column_name)).to eq false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '.filter' do
|
63
|
+
it 'returns the column name if it meets all criterias' do
|
64
|
+
expect(described_class.filter('name','type','type')).to eq 'name'
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'criterias' do
|
68
|
+
it 'filters out polymorphic association columns' do
|
69
|
+
expect(described_class.filter('polymorphic_type','type','type')).to be_nil
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'filters out columns name ending in id' do
|
73
|
+
expect(described_class.filter('message_id','type','type')).to be_nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'filters out columns with no matching types' do
|
77
|
+
expect(described_class.filter('message', :type, [:expected_type])).to be_nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/filtering/truncation'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
module Filtering
|
6
|
+
describe Truncation do
|
7
|
+
describe '.matches_patterns' do
|
8
|
+
it 'returns the tables that are the same as patterns' do
|
9
|
+
patterns = [:pattern_1, :pattern_2]
|
10
|
+
tables = [ :pattern_1, :pattern_2, :non_pattern_1, :pattern_3]
|
11
|
+
truncation_tables = described_class.matches_patterns(tables, patterns)
|
12
|
+
expect(truncation_tables).to match_array [:pattern_1, :pattern_2]
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'returns all the tables that start with the pattern followed by an underscore' do
|
16
|
+
patterns = [:pattern_1, :pattern_2]
|
17
|
+
tables = [ :pattern_1,
|
18
|
+
:pattern_2,
|
19
|
+
:pattern_1_table,
|
20
|
+
:non_pattern_1,
|
21
|
+
:pattern_3,
|
22
|
+
:pattern_1non_truncation_table]
|
23
|
+
|
24
|
+
tables = described_class.matches_patterns(tables, patterns)
|
25
|
+
expect(tables).to match_array [:pattern_1,
|
26
|
+
:pattern_2,
|
27
|
+
:pattern_1_table]
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not have any duplicate table names' do
|
32
|
+
patterns = [:pattern_1, :pattern_1]
|
33
|
+
tables = [:pattern_1]
|
34
|
+
|
35
|
+
tables = described_class.matches_patterns(tables, patterns)
|
36
|
+
expect(tables).to match_array [:pattern_1]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/filtering'
|
3
|
+
module DbObfuscation
|
4
|
+
describe Filtering do
|
5
|
+
|
6
|
+
describe 'obfuscation_config' do
|
7
|
+
it 'returns the obfuscation strategies for every table in the database' do
|
8
|
+
config = {
|
9
|
+
table_1: {
|
10
|
+
field_1: :first_name_strategy,
|
11
|
+
field_2: :default_strategy,
|
12
|
+
date_field: :date_strategy
|
13
|
+
},
|
14
|
+
table_2: {
|
15
|
+
field_1: :default_strategy,
|
16
|
+
date_field: :date_strategy
|
17
|
+
},
|
18
|
+
table_without_any_user_defined_obfuscation_strategies: {
|
19
|
+
field_1: :default_strategy,
|
20
|
+
field_2: :default_strategy
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
expect(described_class.obfuscation_config([:string])).to eq config
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'includes table configuations without any user defined table strategies' do
|
28
|
+
config = described_class.obfuscation_config([:string])
|
29
|
+
expect(config.keys).to include(:table_without_any_user_defined_obfuscation_strategies)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not include configuration for table that does not have obfuscatable columns' do
|
33
|
+
config = described_class.obfuscation_config([:string])
|
34
|
+
expect(config.keys).to_not include(:table_without_any_obfuscatable_columns)
|
35
|
+
expect(config.keys).to include(:table_without_any_user_defined_obfuscation_strategies)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/obfuscation_strategy'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
describe ObfuscationStrategy do
|
6
|
+
describe '.strategy' do
|
7
|
+
it "returns ssn_strategy when column name is 'ssn'" do
|
8
|
+
actual_strategy = described_class.strategy('ssn')
|
9
|
+
expect(actual_strategy).to eq :ssn_strategy
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns ssn_strategy when column name matches 'ssn'" do
|
13
|
+
actual_strategy = described_class.strategy('ssn_number')
|
14
|
+
expect(actual_strategy).to eq :ssn_strategy
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns email_strategy when column name is 'email'" do
|
18
|
+
actual_strategy = described_class.strategy('email')
|
19
|
+
expect(actual_strategy).to eq :email_strategy
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns email_strategy when column name matches 'email'" do
|
23
|
+
actual_strategy = described_class.strategy('contact_person_email')
|
24
|
+
expect(actual_strategy).to eq :email_strategy
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns first_name_strategy when column name is 'first_name'" do
|
28
|
+
actual_strategy = described_class.strategy('first_name')
|
29
|
+
expect(actual_strategy).to eq :first_name_strategy
|
30
|
+
end
|
31
|
+
|
32
|
+
it "returns last_name_strategy when column name is 'last_name'" do
|
33
|
+
actual_strategy = described_class.strategy('last_name')
|
34
|
+
expect(actual_strategy).to eq :last_name_strategy
|
35
|
+
end
|
36
|
+
|
37
|
+
it "returns name_strategy when column name matches 'name'" do
|
38
|
+
actual_strategy = described_class.strategy('name')
|
39
|
+
expect(actual_strategy).to eq :name_strategy
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/obfuscator'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
describe Obfuscator do
|
6
|
+
context 'medicaid recipent identification number' do
|
7
|
+
it 'uses faker numerify' do
|
8
|
+
expect(FFaker).to receive(:numerify).with('############')
|
9
|
+
described_class.obfuscate(:medicaid_id_strategy)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'generates random 12 digits number' do
|
13
|
+
m_id = described_class.obfuscate(:medicaid_id_strategy)
|
14
|
+
expect(m_id.chars.size).to eq(12)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'obfuscates school strategy with a fake school name' do
|
19
|
+
expect(FFaker::Education).to receive(:school)
|
20
|
+
described_class.obfuscate(:school_strategy)
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'unique strategy' do
|
24
|
+
it 'provides unique strings' do
|
25
|
+
expect(FFaker::Name).to receive(:first_name)#.and_return 'whatever'
|
26
|
+
expect(FFaker).to receive(:letterify).with('??????????')
|
27
|
+
expect(FFaker::Name).to receive(:last_name)
|
28
|
+
described_class.obfuscate(:unique_name_strategy)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'provides names comprised of first, random middle and last names' do
|
32
|
+
allow(FFaker::Name).to receive(:first_name).and_return('First')
|
33
|
+
allow(FFaker).to receive(:letterify).with('??????????').and_return('abc')
|
34
|
+
allow(FFaker::Name).to receive(:last_name).and_return('Last')
|
35
|
+
name = 'First abc Last'
|
36
|
+
|
37
|
+
expect(described_class.obfuscate(:unique_name_strategy)).to eq(name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'obfuscates gender as unknown' do
|
42
|
+
expect(described_class.obfuscate(:gender_strategy)).to eq 'Unknown'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'obfuscates default strategy as a random word' do
|
46
|
+
expect(FFaker::Lorem).to receive(:word).and_return('fake_word')
|
47
|
+
obfuscated_fake_word = described_class.obfuscate(
|
48
|
+
:default_strategy)
|
49
|
+
expect(obfuscated_fake_word).to eq 'fake_word'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'obfuscates name strategy as fake name' do
|
53
|
+
expect(FFaker::Name).to receive(:name).and_return('fake_name')
|
54
|
+
obfuscated_fake_name = described_class.obfuscate(
|
55
|
+
:name_strategy)
|
56
|
+
expect(obfuscated_fake_name).to eq 'fake_name'
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'obfuscates first_name strategy as fake first name' do
|
60
|
+
expect(FFaker::Name).to receive(:first_name).and_return('fake_first_name')
|
61
|
+
obfuscated_fake_first_name = described_class.obfuscate(
|
62
|
+
:first_name_strategy)
|
63
|
+
expect(obfuscated_fake_first_name).to eq 'fake_first_name'
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'obfuscates last_name strategy as fake last name' do
|
67
|
+
expect(FFaker::Name).to receive(:last_name).and_return('fake_last_name')
|
68
|
+
obfuscated_fake_last_name = described_class.obfuscate(
|
69
|
+
:last_name_strategy)
|
70
|
+
expect(obfuscated_fake_last_name).to eq 'fake_last_name'
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'obfuscates address strategy as fake address with city/state/zip' do
|
74
|
+
expect(FFaker::Address).to receive(:street_address).
|
75
|
+
and_return('fake_street_address')
|
76
|
+
expect(FFaker::AddressUS).to receive(:zip_code).and_return('10000')
|
77
|
+
expect(FFaker::Address).to receive(:city).and_return('New York')
|
78
|
+
expect(FFaker::AddressUS).to receive(:state_abbr).and_return('NY')
|
79
|
+
obfuscated_fake_address = described_class.obfuscate(
|
80
|
+
:address_strategy)
|
81
|
+
expect(obfuscated_fake_address).
|
82
|
+
to eq 'fake_street_address New York NY 10000'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'obfuscates ssn strategy as fake ssn' do
|
86
|
+
expect(FFaker::SSN).to receive(:ssn).and_return('123-456-7890')
|
87
|
+
obfuscated_fake_ssn = described_class.obfuscate(
|
88
|
+
:ssn_strategy)
|
89
|
+
expect(obfuscated_fake_ssn).to eq '123-456-7890'
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'obfuscates email strategy as fake email' do
|
93
|
+
expect(FFaker::Internet).to receive(:safe_email).
|
94
|
+
and_return('fake_email@fake_email.com')
|
95
|
+
obfuscated_fake_email = described_class.obfuscate(
|
96
|
+
:email_strategy)
|
97
|
+
expect(obfuscated_fake_email).to eq 'fake_email@fake_email.com'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'obfuscsates phone number strategy as fake phone number' do
|
101
|
+
expect(FFaker::PhoneNumber).to receive(:phone_number).
|
102
|
+
and_return('123-456-7890')
|
103
|
+
obfuscated_fake_phone_number = described_class.obfuscate(
|
104
|
+
:phone_number_strategy)
|
105
|
+
expect(obfuscated_fake_phone_number).to eq '123-456-7890'
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'obfuscates nil strategy as nil value' do
|
109
|
+
obfuscated_string = described_class.obfuscate(
|
110
|
+
:nil_strategy)
|
111
|
+
expect(obfuscated_string).to eq nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'obfuscates sentence strategy as fake sentence' do
|
115
|
+
expect(FFaker::Lorem).to receive(:sentence).
|
116
|
+
and_return('this is a fake sentence')
|
117
|
+
obfuscated_string = described_class.obfuscate(
|
118
|
+
:sentence_strategy)
|
119
|
+
expect(obfuscated_string).to eq 'this is a fake sentence'
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'obfuscates paragraph strategy as fake paragraph' do
|
123
|
+
expect(FFaker::Lorem).to receive(:paragraph).
|
124
|
+
and_return('this is a fake paragraph')
|
125
|
+
obfuscated_string = described_class.obfuscate(
|
126
|
+
:paragraph_strategy)
|
127
|
+
expect(obfuscated_string).to eq 'this is a fake paragraph'
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'obfuscates driving license strategy as fake driving license' do
|
131
|
+
expect(FFaker::Identification).to receive(:drivers_license).
|
132
|
+
and_return('123-456-7890')
|
133
|
+
obfuscated_string = described_class.obfuscate(
|
134
|
+
:driving_license_strategy)
|
135
|
+
expect(obfuscated_string).to eq '123-456-7890'
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'the date strategy returns random number of days' do
|
139
|
+
expect(described_class.obfuscate(:date_strategy)).to be_between(31,240)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'obfuscates suffix strategy as fake suffix' do
|
143
|
+
expect(FFaker::Name).to receive(:suffix).
|
144
|
+
and_return('Jr')
|
145
|
+
obfuscated_string = described_class.obfuscate(
|
146
|
+
:suffix_strategy)
|
147
|
+
expect(obfuscated_string).to eq 'Jr'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'db_obfuscation/query_builder'
|
3
|
+
|
4
|
+
module DbObfuscation
|
5
|
+
describe QueryBuilder do
|
6
|
+
describe '.multi_update_sql' do
|
7
|
+
let(:sql_query) do
|
8
|
+
<<-SQL.gsub(/\s{2,}/,' ')
|
9
|
+
UPDATE "table_1"
|
10
|
+
SET "field_1" = CASE
|
11
|
+
WHEN "id" = '1' AND "field_1" IS NOT NULL
|
12
|
+
AND "field_1" != '' THEN 'field_1_1'
|
13
|
+
WHEN "id" = '2' AND "field_1" IS NOT NULL
|
14
|
+
AND "field_1" != '' THEN 'field_1_2'
|
15
|
+
WHEN "id" = '3' AND "field_1" IS NOT NULL
|
16
|
+
AND "field_1" != '' THEN 'field_1_3'
|
17
|
+
ELSE
|
18
|
+
"field_1"
|
19
|
+
END,
|
20
|
+
"field_2" = CASE
|
21
|
+
WHEN "id" = '1' AND "field_2" IS NOT NULL
|
22
|
+
AND "field_2" != '' THEN 'field_2_1'
|
23
|
+
WHEN "id" = '2' AND "field_2" IS NOT NULL
|
24
|
+
AND "field_2" != '' THEN 'field_2_2'
|
25
|
+
WHEN "id" = '3' AND "field_2" IS NOT NULL
|
26
|
+
AND "field_2" != '' THEN 'field_2_3'
|
27
|
+
ELSE
|
28
|
+
"field_2"
|
29
|
+
END
|
30
|
+
WHERE "id" IN ('1','2','3')
|
31
|
+
SQL
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'build sql query for multiple row updates' do
|
35
|
+
columns_and_values = [
|
36
|
+
{ field_1: "'field_1_1'", field_2: "'field_2_1'", id: 1 },
|
37
|
+
{ field_1: "'field_1_2'", field_2: "'field_2_2'", id: 2 },
|
38
|
+
{ field_1: "'field_1_3'", field_2: "'field_2_3'", id: 3 },
|
39
|
+
]
|
40
|
+
|
41
|
+
expect(described_class.multi_update_sql(
|
42
|
+
:table_1, columns_and_values)).to eq sql_query.strip
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'handles single row update' do
|
46
|
+
sql_query = <<-SQL.gsub(/\s{2,}/,' ')
|
47
|
+
UPDATE "table_1"
|
48
|
+
SET "field_1" = CASE
|
49
|
+
WHEN "id" = '1' AND "field_1" IS NOT NULL
|
50
|
+
AND "field_1" != '' THEN 'field_1_1'
|
51
|
+
ELSE
|
52
|
+
"field_1"
|
53
|
+
END,
|
54
|
+
"field_2" = CASE
|
55
|
+
WHEN "id" = '1' AND "field_2" IS NOT NULL
|
56
|
+
AND "field_2" != '' THEN 'field_2_1'
|
57
|
+
ELSE
|
58
|
+
"field_2"
|
59
|
+
END
|
60
|
+
WHERE "id" IN ('1')
|
61
|
+
SQL
|
62
|
+
columns_and_values = [
|
63
|
+
{ field_1: "'field_1_1'", field_2: "'field_2_1'", id: 1 },
|
64
|
+
]
|
65
|
+
expect(described_class.multi_update_sql(
|
66
|
+
:table_1,columns_and_values)).to eq sql_query.strip
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '.where_sql' do
|
71
|
+
it 'generates where sql query' do
|
72
|
+
sql_query = "WHERE \"id\" IN ('1','2')"
|
73
|
+
columns_and_values = [
|
74
|
+
{ first_name: 'first1', last_name: 'last1', id: 1 },
|
75
|
+
{ first_name: 'first2', last_name: 'last2', id: 2 }]
|
76
|
+
expect(described_class.where_sql(columns_and_values)).to eq sql_query
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'generates sql query based on the user supplied constraints' do
|
80
|
+
sql_query = "WHERE \"name\" IN ('1','2')"
|
81
|
+
columns_and_values = [
|
82
|
+
{ first_name: "'first1'", last_name: "'last1'", name: 1 },
|
83
|
+
{ first_name: "'first2'", last_name: "'last2'", name: 2 }]
|
84
|
+
expect(described_class.where_sql(columns_and_values)).to eq sql_query
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '.case_sql' do
|
89
|
+
let(:sql) do
|
90
|
+
<<-SQL.gsub(/\s{2,}/,' ')
|
91
|
+
SET "first_name" = CASE
|
92
|
+
WHEN "id" = '1' AND "first_name" IS NOT NULL
|
93
|
+
AND "first_name" != '' THEN 'first1'
|
94
|
+
WHEN "id" = '2' AND "first_name" IS NOT NULL
|
95
|
+
AND "first_name" != '' THEN 'first2'
|
96
|
+
ELSE
|
97
|
+
"first_name"
|
98
|
+
END,
|
99
|
+
"last_name" = CASE
|
100
|
+
WHEN "id" = '1' AND "last_name" IS NOT NULL
|
101
|
+
AND "last_name" != '' THEN 'last1'
|
102
|
+
WHEN "id" = '2' AND "last_name" IS NOT NULL
|
103
|
+
AND "last_name" != '' THEN 'last2'
|
104
|
+
ELSE
|
105
|
+
"last_name"
|
106
|
+
END
|
107
|
+
SQL
|
108
|
+
end
|
109
|
+
let(:columns) do
|
110
|
+
[
|
111
|
+
{ first_name: "'first1'", last_name: "'last1'", id: 1 },
|
112
|
+
{ first_name: "'first2'", last_name: "'last2'", id: 2 }
|
113
|
+
]
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'generates case sql for multiple columns' do
|
117
|
+
expect(described_class.case_sql(columns,[])).to eq sql.strip
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'generates case sql for single column' do
|
121
|
+
sql = <<-SQL.gsub(/\s{2,}/,' ')
|
122
|
+
SET "first_name" = CASE
|
123
|
+
WHEN "id" = '1' AND "first_name" IS NOT NULL
|
124
|
+
AND "first_name" != '' THEN 'first1'
|
125
|
+
ELSE
|
126
|
+
"first_name"
|
127
|
+
END,
|
128
|
+
"last_name" = CASE
|
129
|
+
WHEN "id" = '1' AND "last_name" IS NOT NULL
|
130
|
+
AND "last_name" != '' THEN 'last1'
|
131
|
+
ELSE
|
132
|
+
"last_name"
|
133
|
+
END
|
134
|
+
SQL
|
135
|
+
columns = [{ first_name: "'first1'", last_name: "'last1'", id: 1 }]
|
136
|
+
expect(described_class.case_sql(columns,[])).to eq sql.strip
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'generates case sql for values with quotes in them' do
|
140
|
+
sql = <<-SQL.gsub(/\s{2,}/,' ')
|
141
|
+
SET "first_name" = CASE
|
142
|
+
WHEN "id" = '1' AND "first_name" IS NOT NULL
|
143
|
+
AND "first_name" != '' THEN 'd''mello'
|
144
|
+
ELSE
|
145
|
+
"first_name"
|
146
|
+
END,
|
147
|
+
"last_name" = CASE
|
148
|
+
WHEN "id" = '1' AND "last_name" IS NOT NULL
|
149
|
+
AND "last_name" != '' THEN 'd''angelo'
|
150
|
+
ELSE
|
151
|
+
"last_name"
|
152
|
+
END
|
153
|
+
SQL
|
154
|
+
columns = [{ first_name: "'d''mello'", last_name: "'d''angelo'", id: 1 }]
|
155
|
+
expect(described_class.case_sql(columns,[])).to eq sql.strip
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'when there is date column' do
|
159
|
+
let(:sql) do
|
160
|
+
<<-SQL.gsub(/\s{2,}/,' ')
|
161
|
+
SET "first_name" = CASE
|
162
|
+
WHEN "id" = '1' AND "first_name" IS NOT NULL
|
163
|
+
AND "first_name" != '' THEN 'first1'
|
164
|
+
WHEN "id" = '2' AND "first_name" IS NOT NULL
|
165
|
+
AND "first_name" != '' THEN 'first2'
|
166
|
+
ELSE
|
167
|
+
"first_name"
|
168
|
+
END,
|
169
|
+
"date_of_birth" = CASE
|
170
|
+
WHEN "id" = '1' AND "date_of_birth" IS NOT NULL
|
171
|
+
THEN "date_of_birth" + INTERVAL '31 DAYS'
|
172
|
+
WHEN "id" = '2' AND "date_of_birth" IS NOT NULL
|
173
|
+
THEN "date_of_birth" + INTERVAL '31 DAYS'
|
174
|
+
ELSE
|
175
|
+
"date_of_birth"
|
176
|
+
END
|
177
|
+
SQL
|
178
|
+
end
|
179
|
+
let(:columns) do
|
180
|
+
[
|
181
|
+
{ first_name: "'first1'", date_of_birth: "31", id: 1 },
|
182
|
+
{ first_name: "'first2'", date_of_birth: "31", id: 2 }
|
183
|
+
]
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'does not include the not empty constraint' do
|
187
|
+
date_columns = [:date_of_birth, :date_of_death]
|
188
|
+
expect(described_class.case_sql(columns, date_columns)).to eq sql.strip
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '.update_sql' do
|
194
|
+
sql_query = <<-SQL.gsub(/\s{2,}/,' ')
|
195
|
+
UPDATE users
|
196
|
+
SET first_name='fake first name',
|
197
|
+
last_name='fake last name'
|
198
|
+
WHERE id=1;
|
199
|
+
SQL
|
200
|
+
|
201
|
+
it 'returns the sql statement for updating a single row' do
|
202
|
+
obfuscated_columns = { first_name: "'fake first name'",
|
203
|
+
last_name: "'fake last name'" }
|
204
|
+
expect(described_class.update_sql(:users, 1 , obfuscated_columns)).
|
205
|
+
to eq sql_query.strip
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'updates date field using interval' do
|
209
|
+
sql_query = <<-SQL.gsub(/\s{2,}/,' ')
|
210
|
+
UPDATE users
|
211
|
+
SET first_name='fake first name',
|
212
|
+
last_name='fake last name',
|
213
|
+
date_of_birth=date_of_birth + INTERVAL '31 DAYS'
|
214
|
+
WHERE id=1;
|
215
|
+
SQL
|
216
|
+
obfuscated_columns = { first_name: "'fake first name'",
|
217
|
+
last_name: "'fake last name'",
|
218
|
+
date_of_birth: "31"}
|
219
|
+
date_columns = [:date_of_birth]
|
220
|
+
expect(described_class.update_sql(:users,
|
221
|
+
1,
|
222
|
+
obfuscated_columns,
|
223
|
+
date_columns)).
|
224
|
+
to eq sql_query.strip
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '.column_sql' do
|
229
|
+
it 'returns the sql statement for updating the column values' do
|
230
|
+
sql_query = <<-SQL.gsub(/\s{2,}/,' ')
|
231
|
+
SET
|
232
|
+
first_name='fake first name',
|
233
|
+
last_name='fake last name'
|
234
|
+
SQL
|
235
|
+
obfuscated_columns = { first_name: "'fake first name'",
|
236
|
+
last_name: "'fake last name'" }
|
237
|
+
date_columns = [:date_of_birth]
|
238
|
+
expect(described_class.column_sql(obfuscated_columns, date_columns)).
|
239
|
+
to eq sql_query.strip
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'updates date fields using interval method' do
|
243
|
+
sql_query = <<-SQL.gsub(/\s{2,}/,' ')
|
244
|
+
SET
|
245
|
+
first_name='fake first name',
|
246
|
+
last_name='fake last name',
|
247
|
+
date_of_birth=date_of_birth + INTERVAL '31 DAYS'
|
248
|
+
SQL
|
249
|
+
obfuscated_columns = { first_name: "'fake first name'",
|
250
|
+
last_name: "'fake last name'",
|
251
|
+
date_of_birth: "31"}
|
252
|
+
date_columns = [:date_of_birth]
|
253
|
+
expect(described_class.column_sql(obfuscated_columns, date_columns)).
|
254
|
+
to eq sql_query.strip
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|