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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/Gemfile +13 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE +21 -0
  6. data/README.md +121 -0
  7. data/TODO +15 -0
  8. data/bin/console +25 -0
  9. data/bin/db_obfuscation +121 -0
  10. data/bin/obfuscation_test +54 -0
  11. data/cli/db_dump.rb +29 -0
  12. data/cli/migrator.rb +26 -0
  13. data/cli/seeder.rb +52 -0
  14. data/db_obfuscation.gemspec +25 -0
  15. data/features/bin/dump.feature +21 -0
  16. data/features/bin/obfuscation.feature +12 -0
  17. data/features/bin/test_database_tasks.feature +16 -0
  18. data/features/support.rb +1 -0
  19. data/lib/db_obfuscation.rb +50 -0
  20. data/lib/db_obfuscation/batch_formulator.rb +26 -0
  21. data/lib/db_obfuscation/config.rb +43 -0
  22. data/lib/db_obfuscation/database.rb +8 -0
  23. data/lib/db_obfuscation/environment.rb +14 -0
  24. data/lib/db_obfuscation/filtering.rb +56 -0
  25. data/lib/db_obfuscation/filtering/column.rb +40 -0
  26. data/lib/db_obfuscation/filtering/truncation.rb +18 -0
  27. data/lib/db_obfuscation/obfuscation_strategy.rb +22 -0
  28. data/lib/db_obfuscation/obfuscator.rb +65 -0
  29. data/lib/db_obfuscation/query_builder.rb +62 -0
  30. data/lib/db_obfuscation/truncation.rb +39 -0
  31. data/lib/db_obfuscation/util/trigger.rb +83 -0
  32. data/lib/db_obfuscation/version.rb +4 -0
  33. data/spec/cli/db_dump_spec.rb +33 -0
  34. data/spec/cli/migrator_spec.rb +59 -0
  35. data/spec/cli/seeder_spec.rb +33 -0
  36. data/spec/config/database.yml +5 -0
  37. data/spec/config/table_strategies/table_1.yml +3 -0
  38. data/spec/config/table_strategies/table_2.yml +4 -0
  39. data/spec/config/table_strategies/truncation_table_1.yml +3 -0
  40. data/spec/config/table_strategies/whitelisted_table_1.yml +3 -0
  41. data/spec/config/truncation_patterns.yml +2 -0
  42. data/spec/config/whitelisted_tables.yml +1 -0
  43. data/spec/db_obfuscation/batch_formulator_spec.rb +36 -0
  44. data/spec/db_obfuscation/config_spec.rb +60 -0
  45. data/spec/db_obfuscation/database_spec.rb +10 -0
  46. data/spec/db_obfuscation/filtering/column_spec.rb +82 -0
  47. data/spec/db_obfuscation/filtering/truncation_spec.rb +41 -0
  48. data/spec/db_obfuscation/filtering_spec.rb +39 -0
  49. data/spec/db_obfuscation/obfuscation_strategy_spec.rb +43 -0
  50. data/spec/db_obfuscation/obfuscator_spec.rb +150 -0
  51. data/spec/db_obfuscation/query_builder_spec.rb +259 -0
  52. data/spec/db_obfuscation/truncation_spec.rb +31 -0
  53. data/spec/db_obfuscation/util/trigger_spec.rb +126 -0
  54. data/spec/integration/obfuscation_spec.rb +69 -0
  55. data/spec/spec_helper.rb +3 -0
  56. data/spec/test_db_setup/migrations/1_add_table_1.rb +18 -0
  57. data/spec/test_db_setup/migrations/2_add_table_2.rb +19 -0
  58. data/spec/test_db_setup/migrations/3_add_truncation_table_1.rb +14 -0
  59. data/spec/test_db_setup/migrations/4_add_whitelisted_table_1.rb +14 -0
  60. data/spec/test_db_setup/migrations/5_add_table_without_any_user_defined_obfuscation_strategies.rb +18 -0
  61. data/spec/test_db_setup/migrations/6_add_table_without_any_obfuscatable_columns.rb +15 -0
  62. data/spec/test_db_setup/migrations/7_add_audit_truncation_table.rb +13 -0
  63. data/spec/test_db_setup/seeds/audit_truncation_table.yml +7 -0
  64. data/spec/test_db_setup/seeds/table_1.yml +13 -0
  65. data/spec/test_db_setup/seeds/table_2.yml +15 -0
  66. data/spec/test_db_setup/seeds/table_without_any_obfuscatable_columns.yml +7 -0
  67. data/spec/test_db_setup/seeds/table_without_any_user_defined_obfuscation_strategies.yml +13 -0
  68. data/spec/test_db_setup/seeds/truncation_table_1.yml +9 -0
  69. data/spec/test_db_setup/seeds/whitelisted_table_1.yml +9 -0
  70. 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,10 @@
1
+ require 'spec_helper'
2
+ require 'db_obfuscation/database'
3
+
4
+ module DbObfuscation
5
+ describe DbObfuscation do
6
+ it 'connects to database' do
7
+ expect(DB.adapter_scheme).to eq(:postgres)
8
+ end
9
+ end
10
+ 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
+