crypt_keeper 0.22.0 → 1.0.0.beta1

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +5 -1
  4. data/Appraisals +6 -6
  5. data/README.md +8 -20
  6. data/Rakefile +7 -3
  7. data/crypt_keeper.gemspec +5 -5
  8. data/gemfiles/{activerecord_4_1.gemfile → activerecord_5_0.gemfile} +2 -2
  9. data/lib/crypt_keeper.rb +1 -2
  10. data/lib/crypt_keeper/helper.rb +0 -18
  11. data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +7 -9
  12. data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +7 -9
  13. data/lib/crypt_keeper/model.rb +14 -20
  14. data/lib/crypt_keeper/provider/aes_new.rb +1 -1
  15. data/lib/crypt_keeper/provider/base.rb +21 -0
  16. data/lib/crypt_keeper/provider/mysql_aes_new.rb +1 -1
  17. data/lib/crypt_keeper/provider/postgres_pgp.rb +2 -2
  18. data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +1 -1
  19. data/lib/crypt_keeper/version.rb +1 -1
  20. data/spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb +56 -0
  21. data/spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb +94 -0
  22. data/spec/crypt_keeper/model_spec.rb +172 -0
  23. data/spec/crypt_keeper/provider/aes_new_spec.rb +41 -0
  24. data/spec/crypt_keeper/provider/mysql_aes_new_spec.rb +50 -0
  25. data/spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb +66 -0
  26. data/spec/crypt_keeper/provider/postgres_pgp_spec.rb +66 -0
  27. data/spec/spec_helper.rb +0 -1
  28. data/spec/support/encryptors.rb +9 -3
  29. data/spec/support/logging.rb +92 -0
  30. metadata +37 -44
  31. data/gemfiles/activerecord_4_1.gemfile.lock +0 -120
  32. data/gemfiles/activerecord_4_2.gemfile.lock +0 -120
  33. data/lib/crypt_keeper/provider/aes.rb +0 -66
  34. data/lib/crypt_keeper/provider/mysql_aes.rb +0 -47
  35. data/spec/log_subscriber/mysql_aes_spec.rb +0 -73
  36. data/spec/log_subscriber/postgres_pgp_spec.rb +0 -123
  37. data/spec/model_spec.rb +0 -169
  38. data/spec/provider/aes_new_spec.rb +0 -45
  39. data/spec/provider/aes_spec.rb +0 -67
  40. data/spec/provider/mysql_aes_new_spec.rb +0 -54
  41. data/spec/provider/mysql_aes_spec.rb +0 -35
  42. data/spec/provider/postgres_pgp_public_key_spec.rb +0 -70
  43. data/spec/provider/postgres_pgp_spec.rb +0 -70
@@ -0,0 +1,94 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::LogSubscriber::PostgresPgp do
4
+ before do
5
+ CryptKeeper.silence_logs = false
6
+ end
7
+
8
+ use_postgres
9
+
10
+ context "Symmetric encryption" do
11
+ # Fire the ActiveSupport.on_load
12
+ before do
13
+ CryptKeeper::Provider::PostgresPgp.new key: 'secret'
14
+ end
15
+
16
+ let(:input_query) do
17
+ "SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
18
+ end
19
+
20
+ let(:output_query) do
21
+ "SELECT encrypt([FILTERED]) FROM DUAL;"
22
+ end
23
+
24
+ let(:input_search_query) do
25
+ "SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE ((pgp_sym_decrypt('f'), 'tool') = 'blah')) AND secret = 'testing'"
26
+ end
27
+
28
+ let(:output_search_query) do
29
+ "SELECT \"sensitive_data\".* FROM \"sensitive_data\" WHERE decrypt([FILTERED]) AND secret = 'testing'"
30
+ end
31
+
32
+ it "filters pgp functions" do
33
+ should_log_scrubbed_query(input: input_query, output: output_query)
34
+ end
35
+
36
+ it "filters pgp functions in lowercase" do
37
+ should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
38
+ end
39
+
40
+ it "filters pgp functions when searching" do
41
+ should_log_scrubbed_query(input: input_search_query, output: output_search_query)
42
+ end
43
+
44
+ it "forces string encodings" do
45
+ input_query = "SELECT pgp_sym_encrypt('hi \255', 'test') FROM DUAL;"
46
+
47
+ should_log_scrubbed_query(input: input_query, output: output_query)
48
+ end
49
+
50
+ it "skips logging if CryptKeeper.silence_logs is set" do
51
+ CryptKeeper.silence_logs = true
52
+
53
+ should_not_log_query(input_query)
54
+ end
55
+ end
56
+
57
+ context "Public key encryption" do
58
+ let(:public_key) do
59
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
60
+ end
61
+
62
+ let(:private_key) do
63
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
64
+ end
65
+
66
+ # Fire the ActiveSupport.on_load
67
+ before do
68
+ CryptKeeper::Provider::PostgresPgpPublicKey.new key: 'secret', public_key: public_key, private_key: private_key
69
+ end
70
+
71
+ let(:input_query) do
72
+ "SELECT pgp_pub_encrypt('test', dearmor('#{public_key}
73
+ '))"
74
+ end
75
+
76
+ let(:output_query) do
77
+ "SELECT encrypt([FILTERED])"
78
+ end
79
+
80
+ it "filters pgp functions" do
81
+ should_log_scrubbed_query(input: input_query, output: output_query)
82
+ end
83
+
84
+ it "filters pgp functions in lowercase" do
85
+ should_log_scrubbed_query(input: input_query.downcase, output: output_query.downcase.gsub(/filtered/, 'FILTERED'))
86
+ end
87
+
88
+ it "skips logging if CryptKeeper.silence_logs is set" do
89
+ CryptKeeper.silence_logs = true
90
+
91
+ should_not_log_query(input_query)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe CryptKeeper::Model do
6
+ use_sqlite
7
+
8
+ subject { create_model }
9
+
10
+ after do
11
+ CryptKeeper.stub_encryption = false
12
+ end
13
+
14
+ describe "#crypt_keeper" do
15
+ context "Fields" do
16
+ it "enables encryption for the given fields" do
17
+ subject.crypt_keeper :storage, :secret, encryptor: :fake_encryptor
18
+ expect(subject.crypt_keeper_fields).to eq([:storage, :secret])
19
+ end
20
+
21
+ it "raises an exception for missing field" do
22
+ msg = "Column :none does not exist"
23
+ subject.crypt_keeper :none, encryptor: :fake_encryptor
24
+ expect { subject.new.save }.to raise_error(ArgumentError, msg)
25
+ end
26
+
27
+ it "raises an exception for non text fields" do
28
+ msg = "Column :name must be of type 'text' to be used for encryption"
29
+ subject.crypt_keeper :name, encryptor: :fake_encryptor
30
+ expect { subject.new.save }.to raise_error(ArgumentError, msg)
31
+ end
32
+ end
33
+
34
+ context "Options" do
35
+ it "accepts the class name as a string" do
36
+ subject.crypt_keeper :storage, :secret, key1: 1, key2: 2, encryptor: "FakeEncryptor"
37
+ expect(subject.send(:encryptor_klass)).to eq(CryptKeeper::Provider::FakeEncryptor)
38
+ end
39
+
40
+ it "raises an error on missing encryptor" do
41
+ expect { subject.crypt_keeper :storage, :secret }.
42
+ to raise_error(ArgumentError, /You must specify a valid encryptor/)
43
+ end
44
+
45
+ it "raises an error on encryptor without base" do
46
+ expect { subject.crypt_keeper :storage, encryptor: "InvalidEncryptor" }.
47
+ to raise_error(ArgumentError, /You must specify a valid encryptor/)
48
+ end
49
+ end
50
+ end
51
+
52
+ context "Encryption and Decryption" do
53
+ let(:plain_text) { 'plain_text' }
54
+ let(:cipher_text) { 'tooltxet_nialp' }
55
+
56
+ subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :encryptor }
57
+
58
+ it "encrypts the data" do
59
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:encrypt).with('testing')
60
+ subject.create!(storage: 'testing')
61
+ end
62
+
63
+ it "decrypts the data" do
64
+ record = subject.create!(storage: 'testing')
65
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:decrypt).at_least(1).times.with('toolgnitset')
66
+ subject.find(record.id).storage
67
+ end
68
+
69
+ it "returns the plaintext on decrypt" do
70
+ record = subject.create!(storage: 'testing')
71
+ expect(subject.find(record.id).storage).to eq('testing')
72
+ end
73
+
74
+ it "does not encrypt or decrypt nil" do
75
+ data = subject.create!(storage: nil)
76
+ expect(data.storage).to be_nil
77
+ end
78
+
79
+ it "does not encrypt or decrypt empty strings" do
80
+ data = subject.create!(storage: "")
81
+ expect(data.storage).to be_empty
82
+ end
83
+
84
+ it "converts numbers to strings" do
85
+ data = subject.create!(storage: 1)
86
+ expect(data.reload.storage).to eq("1")
87
+ end
88
+
89
+ it "does not decrypt when stubbing is enabled" do
90
+ CryptKeeper.stub_encryption = true
91
+ record = subject.create!(storage: "testing")
92
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
93
+ subject.find(record.id).storage
94
+ end
95
+
96
+ it "does not decrypt when stubbing is enabled after model is created" do
97
+ record = subject.create!(storage: "testing")
98
+ CryptKeeper.stub_encryption = true
99
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
100
+ subject.find(record.id).storage
101
+ end
102
+ end
103
+
104
+ context "Search" do
105
+ subject { create_encrypted_model :storage, passphrase: 'tool', encryptor: :search_encryptor }
106
+
107
+ it "searches if supported" do
108
+ expect { subject.search_by_plaintext(:storage, 'test1') }.to_not raise_error
109
+ end
110
+
111
+ it "complains about bad columns" do
112
+ expect { subject.search_by_plaintext(:what, 'test1') }.to raise_error(/what is not a crypt_keeper field/)
113
+ end
114
+ end
115
+
116
+ context "Encodings" do
117
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
118
+
119
+ it "forces the encoding on decrypt" do
120
+ record = subject.create!(storage: 'Tromsø')
121
+ record.reload
122
+ expect(record.storage).to eql('Tromsø')
123
+ end
124
+
125
+ it "converts from other encodings" do
126
+ plaintext = "\xC2\xA92011 AACR".force_encoding('ASCII-8BIT')
127
+ record = subject.create!(storage: plaintext)
128
+ record.reload
129
+ expect(record.storage.encoding.name).to eql('UTF-8')
130
+ end
131
+ end
132
+
133
+ context "Initial Table Encryption" do
134
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
135
+
136
+ before do
137
+ subject.delete_all
138
+ c = create_model
139
+ 5.times { |i| c.create! storage: "testing#{i}" }
140
+ end
141
+
142
+ it "encrypts the table" do
143
+ expect { subject.first(5).map(&:storage) }.to raise_error(OpenSSL::Cipher::CipherError)
144
+ subject.encrypt_table!
145
+ expect { subject.first(5).map(&:storage) }.not_to raise_error
146
+ end
147
+ end
148
+
149
+ context "Table Decryption (Reverse of Initial Table Encryption)" do
150
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
151
+ let!(:storage_entries) { 5.times.map { |i| "testing#{i}" } }
152
+
153
+ before do
154
+ subject.delete_all
155
+ storage_entries.each { |entry| subject.create! storage: entry}
156
+ end
157
+
158
+ it "decrypts the table" do
159
+ subject.decrypt_table!
160
+ expect( create_model.first(5).map(&:storage) ).to eq( storage_entries )
161
+ end
162
+ end
163
+
164
+ context "Missing Attributes" do
165
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
166
+
167
+ it "doesn't attempt decryption of missing attributes" do
168
+ subject.create!(storage: 'blah')
169
+ expect { subject.select(:id).first }.to_not raise_error
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::AesNew do
4
+ subject { described_class.new(key: 'cake', salt: 'salt') }
5
+
6
+ describe "#initialize" do
7
+ let(:digested_key) do
8
+ ::Armor.digest('cake', 'salt')
9
+ end
10
+
11
+ specify { expect(subject.key).to eq(digested_key) }
12
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
13
+ end
14
+
15
+ describe "#encrypt" do
16
+ let(:encrypted) do
17
+ subject.encrypt 'string'
18
+ end
19
+
20
+ specify { expect(encrypted).to_not eq('string') }
21
+ specify { expect(encrypted).to_not be_blank }
22
+ end
23
+
24
+ describe "#decrypt" do
25
+ let(:decrypted) do
26
+ subject.decrypt "V02ebRU2wLk25AizasROVg==$kE+IpRaUNdBfYqR+WjMqvA=="
27
+ end
28
+
29
+ specify { expect(decrypted).to eq('string') }
30
+ end
31
+
32
+ describe "#search" do
33
+ let(:records) do
34
+ [{ name: 'Bob' }, { name: 'Tim' }]
35
+ end
36
+
37
+ it "finds the matching record" do
38
+ expect(subject.search(records, :name, 'Bob')).to eql([records.first])
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::MysqlAesNew do
4
+ use_mysql
5
+
6
+ let(:plain_text) { 'test' }
7
+
8
+ # MySQL stores AES encrypted strings in binary which you can't paste
9
+ # into a spec :). This is a Base64 encoded string of 'test' AES encrypted
10
+ # by AES_ENCRYPT()
11
+ let(:cipher_text) do
12
+ "fBN8i7bx/DGAA4NJ4EWi0A=="
13
+ end
14
+
15
+ subject { described_class.new key: ENCRYPTION_PASSWORD, salt: 'salt' }
16
+
17
+ specify { expect(subject.key).to eq("825e8c5e8ca394818b307b22b8cb7d3df2735e9c1e5838b476e7719135a4f499f2133022c1a0e8597c9ac1507b0f0c44328a40049f9704fab3598c5dec120724") }
18
+
19
+ describe "#initialize" do
20
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
21
+ specify { expect { described_class.new(key: 'blah') }.to raise_error(ArgumentError, "Missing :salt") }
22
+ end
23
+
24
+ describe "#encrypt" do
25
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
26
+ specify { expect(subject.encrypt(plain_text)).to_not be_blank }
27
+ end
28
+
29
+ describe "#decrypt" do
30
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
31
+ end
32
+
33
+ describe "#search" do
34
+ subject { mysql_model }
35
+
36
+ it "finds the matching record" do
37
+ subject.create!(storage: 'blah2')
38
+ match = subject.create!(storage: 'blah')
39
+ expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
40
+ end
41
+
42
+ it "keeps the scope" do
43
+ subject.create!(storage: 'blah')
44
+ subject.create!(storage: 'blah')
45
+
46
+ scope = subject.limit(1)
47
+ expect(scope.search_by_plaintext(:storage, 'blah').count).to eq(1)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::PostgresPgpPublicKey do
4
+ use_postgres
5
+
6
+ let(:cipher_text) { '\xc1c04c036c401ad086beb9e3010800987d6c4ccd974322190caa75a3a01aba37bc1970182c4c1d3faec98edf186780520f0586101f286e0626096a1eca91a229ed4d4058a6913a8d13cdf49f29ea44e2b96d10347f9b1b860bb3c959f000a3b1b415a95d2cd07af8c74aa6df8cd10ab06b6a6f7db69cdf3185466d68c5b66b95b813acdfb3ddfb021cac92e0967d67e90df73332f27970c1d2b9a56ac74f602d4107b163ed73ef89fca560d9a0a0d2bc7a74005f29fa27babfbaf950ac07b1c809049db4ab126be4824cf76416c278571f7064f638edf830a1ae5ee1ab544d35fce0f974f21b9dcbbea3986077d27b0de34144dc23f369f471090b57e067a056901e680493ddf2a6b29e4af3462387d235010259556079d07daa249b6703e2bc79345da556cfb46f228cad40a8a5b569ac46f08865f9176acf89129a3e0ceb2a7b1991012f65' }
7
+
8
+ let(:integer_cipher_text) { '\xc1c04c036c401ad086beb9e30107ff59e674ba05958eb053c2427b44355e0f333f1726e18a0b851130130510c648f580b13b3f6a223eb26e397008596867c5a511a4f5bfbf2ecc852d8929814480d63166e525fa2b259b6a8d4474b5b1373b4e1a4fe70a491d25442e1c0046fd3d69466ad30153c8d8d920e9b4260d4e4e421ef3ead162b3aba5d85408c4ef9f9d342b5655c7568d1bdc61c27ddb419133bf091f22f42e7bc91ec6d279b7b25b87ea65119568b85ae81079dd0a6a7258b58fb219c6cc4580f33cb46de97770a1eb0880bdf87426fd0529576a1e791e521d9b3c426e393e63d83321f319b00f9dc4027ea5a81dd57c0f5ba868fb86d73179c34f2287c437266e8becc072b45a929562d2320194be54464e03854635d0f7d7fb10813adbc6efe51efa9095a9bacc2a03fb5c41d1c1896384e4f36b100c0f00e81d4cff7d' }
9
+
10
+ let(:integer_plain_text) { 1 }
11
+ let(:plain_text) { 'test' }
12
+
13
+ let(:public_key) do
14
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'public.asc'))
15
+ end
16
+
17
+ let(:private_key) do
18
+ IO.read(File.join(SPEC_ROOT, 'fixtures', 'private.asc'))
19
+ end
20
+
21
+ subject { described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key, private_key: private_key }
22
+
23
+
24
+ specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
25
+
26
+ describe "#initialize" do
27
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
28
+ end
29
+
30
+ describe "#encrypt" do
31
+ context "Strings" do
32
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
33
+ specify { expect(subject.encrypt(plain_text)).to_not be_empty }
34
+
35
+ it "does not double encrypt" do
36
+ pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
37
+ expect(pgp.encrypt(cipher_text)).to eq(cipher_text)
38
+ end
39
+ end
40
+
41
+ context "Integers" do
42
+ specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
43
+ specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
44
+ end
45
+ end
46
+
47
+ describe "#decrypt" do
48
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
49
+ specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
50
+
51
+ it "does not decrypt w/o private key" do
52
+ pgp = described_class.new key: ENCRYPTION_PASSWORD, public_key: public_key
53
+ expect(pgp.decrypt(cipher_text)).to eq(cipher_text)
54
+ end
55
+ end
56
+
57
+ describe "#encrypted?" do
58
+ it "returns true for encrypted strings" do
59
+ expect(subject.encrypted?(cipher_text)).to be_truthy
60
+ end
61
+
62
+ it "returns false for non-encrypted strings" do
63
+ expect(subject.encrypted?(plain_text)).to be_falsey
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::PostgresPgp do
4
+ use_postgres
5
+
6
+ let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
7
+ let(:plain_text) { 'test' }
8
+
9
+ let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }
10
+
11
+ let(:integer_plain_text) { 1 }
12
+
13
+ subject { described_class.new key: ENCRYPTION_PASSWORD }
14
+
15
+ specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }
16
+
17
+ describe "#initialize" do
18
+ specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
19
+ end
20
+
21
+ describe "#encrypt" do
22
+ context "Strings" do
23
+ specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
24
+ specify { expect(subject.encrypt(plain_text)).to_not be_empty }
25
+ end
26
+
27
+ context "Integers" do
28
+ specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
29
+ specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
30
+ end
31
+ end
32
+
33
+ describe "#decrypt" do
34
+ specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
35
+ specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
36
+ end
37
+
38
+ describe "#search" do
39
+ subject { postgres_model }
40
+
41
+ it "finds the matching record" do
42
+ subject.create!(storage: 'blah2')
43
+ match = subject.create!(storage: 'blah')
44
+ expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
45
+ end
46
+ end
47
+
48
+ describe "Custom pgcrypto options" do
49
+ let(:pgcrypto_options) { 'compress-level=0' }
50
+
51
+ subject { described_class.new key: 'candy', pgcrypto_options: pgcrypto_options }
52
+
53
+ it "reads and writes" do
54
+ queries = logged_queries do
55
+ encrypted = subject.encrypt(plain_text)
56
+ expect(subject.decrypt(encrypted)).to eq(plain_text)
57
+ end
58
+
59
+ expect(queries).to_not be_empty
60
+
61
+ queries.select { |query| query.include?("pgp_sym_encrypt") }.each do |q|
62
+ expect(q).to include(pgcrypto_options)
63
+ end
64
+ end
65
+ end
66
+ end