Reputation: 8212
I want to test a model that uses attr_encrypted to encrypt a secret in the database
class Thing
attr_encrypted :secret, encode: true
end
But when I define the secret in a fixture the encoded newline character gets escaped out.
one:
encrypted_secret: '<%= Thing.encrypt_secret(SecureRandom.uuid) %>'
That is:
'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC\nm6qD\n'
Is stored in the database as:
'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC
m6qD'
The problem with this is that this then fails:
thing = things(:one)
assert_equal thing, Thing.find_by_secret(thing.secret)
Thing.find_by_secret(thing.secret) returns nil because the resulting SQL query tries to match the two versions of the encryped secret and fails to get a match.
I have tried:
one:
encrypted_secret: 'axZFZEknxUSYdUlPhwLBbj8CwSeCW5at2INA98EcCcY7MVFdmXvk7Sb4DZhC\nm6qD\n'
but get the same result.
How can I configure my fixtures to work with attr_encrypted?
Upvotes: 5
Views: 1016
Reputation: 1199
I faced the same situation under Rails4 + attr_encrypted + fixture + Minitest environment, and here my workaround is.
In summary, I had the following steps:
Let me explain the detail below.
For example, "Message" model has two attributes 'name' and 'body' which are required to be encrypted as follows:
class Message < ActiveRecord::Base
attr_encrypted :name, key: ...
attr_encrypted :body, key: ...
...
end
msg1: name: Hello body: Hello, I am here... msg2: name: How are you body: Good morning, ...
require 'active_record/fixtures' src_yml = 'test/fixtures/messages.yml.noenc' dest_yml = 'test/fixtures/messages.yml' task 'test' => dest_yml namespace :[MY_APP] do desc "generate encrypted fixture" file dest_yml => src_yml do |t| require Rails.root + 'config/environment' encrypted_hash = {} for k, v in YAML.load(ERB.new(File.read(Rails.root + src_yml)).result) do msg = Message.new(v.merge([ANY ADDITIONAL ATTRS])) encrypted_hash[k] = { 'encrypted_name' => msg.encrypted_name, 'encrypted_name_iv' => msg.encrypted_name_iv, 'encrypted_body' => msg.encrypted_body, 'encrypted_body_iv' => msg.encrypted_body_iv, [ANY ADDITIONAL KEY_N_VALUE] } end File.open(Rails.root + t.name, 'w') do |f| f.write(<<EOH) #---------------------------------------------------------------------- # DO NOT MODIFY THIS FILE!! # # This file is generated from #{src_yml} by: # # (edit #{src_yml}) # $ rake [MY_APP]:generate_fixture, or # $ rake #---------------------------------------------------------------------- EOH f.write(encrypted_hash.to_yaml) end end end
Please substitute [MY_APP], [ANY ADDITIONAL ATTRS], and [ANY ADDITIONAL KEY_N_VALUE] to actual values.
Then, 'rake' or 'rake test' checks file dependency between messages.yml.noenc and messages.yml, and generate messages.yml when necessary before 'rake test'.
Upvotes: 0
Reputation: 8212
A solution that works is to replace all '\n' with '\\n' and use double quotes. This works:
one:
encryped_secret: "<%= Thing.encrypt_secret(SecureRandom.uuid).gsub(/\n/, '\\\\n') %>"
Is there a tidier way to do this?
Upvotes: 0