db_obfuscation 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+