require 'spec_helper'

describe CryptKeeper::Provider::PostgresPgp do
  use_postgres

  let(:cipher_text) { '\xc30d04070302f1a092093988b26873d235017203ce086a53fce1925dc39b4e972e534f192d10b94af3dcf8589abc1f828456f5d3e20b225d56006ffd1e312e3b8a492a6010e9' }
  let(:plain_text)  { 'test' }

  let(:integer_cipher_text) { '\xc30d04070302c8d266353bcf2fc07dd23201153f9d9c32fbb3c36b9b0db137bf8b6c609172210d89ded63f11dff23d1ddbf5111c0266549dde26175c4425e06bb4bd6f' }

  let(:integer_plain_text) { 1 }

  subject { described_class.new key: ENCRYPTION_PASSWORD }

  specify { expect(subject.key).to eq(ENCRYPTION_PASSWORD) }

  describe "#initialize" do
    specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
  end

  describe "#encrypt" do
    context "Strings" do
      specify { expect(subject.encrypt(plain_text)).to_not eq(plain_text) }
      specify { expect(subject.encrypt(plain_text)).to_not be_empty }
    end

    context "Integers" do
      specify { expect(subject.encrypt(integer_plain_text)).to_not eq(integer_plain_text) }
      specify { expect(subject.encrypt(integer_plain_text)).to_not be_empty }
    end

    it "filters StatementInvalid errors" do
      subject.pgcrypto_options = "invalid"

      begin
        subject.encrypt(plain_text)
      rescue ActiveRecord::StatementInvalid => e
        expect(e.message).to_not include("invalid")
        expect(e.message).to_not include(ENCRYPTION_PASSWORD)
      end
    end
  end

  describe "#decrypt" do
    specify { expect(subject.decrypt(cipher_text)).to eq(plain_text) }
    specify { expect(subject.decrypt(integer_cipher_text)).to eq(integer_plain_text.to_s) }
    specify { expect(subject.decrypt(plain_text)).to eq(plain_text) }

    it "filters StatementInvalid errors" do
      begin
        subject.decrypt("invalid")
      rescue ActiveRecord::StatementInvalid => e
        expect(e.message).to_not include("invalid")
        expect(e.message).to_not include(ENCRYPTION_PASSWORD)
      end
    end
  end

  describe "#search" do
    subject { postgres_model }

    it "finds the matching record" do
      subject.create!(storage: 'blah2')
      match = subject.create!(storage: 'blah')
      expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
    end

    it "successfully accesses result when the data has empty strings and nil" do
      subject.create!(storage: nil)
      subject.create!(storage: '')
      subject.create!(storage: '   ')
      match = subject.create!(storage: 'blah')

      results = subject.search_by_plaintext(:storage, 'blah')

      expect(results).to match_array([match])
    end

    it "finds an empty string" do
      subject.create!(storage: nil)
      subject.create!(storage: 'blah')
      match = subject.create!(storage: '')

      expect(subject.search_by_plaintext(:storage, '')).to match_array([match])
    end

    it "finds the empty string with the right number of spaces" do
      subject.create!(storage: '')
      match = subject.create!(storage: '   ')

      results = subject.search_by_plaintext(:storage, '   ')

      expect(results).to match_array([match])
    end

    it "finds nil results" do
      subject.create!(storage: '')
      subject.create!(storage: 'blah')
      match = subject.create!(storage: nil)

      expect(subject.search_by_plaintext(:storage, nil)).to match_array([match])
    end
  end

  describe "Custom pgcrypto options" do
    let(:pgcrypto_options) { 'compress-level=0' }

    subject { described_class.new key: 'candy', pgcrypto_options: pgcrypto_options }

    it "reads and writes" do
      queries = logged_queries do
        encrypted = subject.encrypt(plain_text)
        expect(subject.decrypt(encrypted)).to eq(plain_text)
      end

      expect(queries).to_not be_empty

      queries.select { |query| query.include?("pgp_sym_encrypt") }.each do |q|
        expect(q).to include(pgcrypto_options)
      end
    end
  end
end
