<commit_before>require 'spec_helper'
require 'openssl'
require 'ruby_event_store/spec/mapper_lint'

module RubyEventStore
  module Mappers
    TicketTransferred = Class.new(RubyEventStore::Event) do
      def self.encryption_schema
        {
          sender: {
            name:  ->(data) { data.dig(:sender, :user_id) },
            email: ->(data) { data.dig(:sender, :user_id) },
          },
          recipient: {
            name:  ->(data) { data.dig(:recipient, :user_id) },
            email: ->(data) { data.dig(:recipient, :user_id) },
          }
        }
      end
    end

    TicketCancelled = Class.new(RubyEventStore::Event)

    TicketHolderEmailProvided = Class.new(RubyEventStore::Event) do
      def self.encryption_schema
        {
          email: ->(data) do
            data.fetch(:user_id)
          end
        }
      end
    end

    RSpec.describe Encryption do
      let(:key_repository) { InMemoryEncryptionKeyRepository.new }
      let(:serializer)     { YAML }
      let(:mapper)         { Encryption.new(key_repository, serializer: serializer) }
      let(:sender_id)      { SecureRandom.uuid }
      let(:recipient_id)   { SecureRandom.uuid }
      let(:event_id)       { SecureRandom.uuid }
      let(:ticket_id)      { SecureRandom.uuid }
      let(:correlation_id) { SecureRandom.uuid }
      let(:sender_email)   { 'alice@universe' }
      let(:recipient) do
        {
          user_id: recipient_id,
          name: 'Bob',
          email: 'bob@universe',
        }
      end
      let(:sender) do
        {
          user_id: sender_id,
          name: 'Alice',
          email: sender_email
        }
      end
      let(:data) do
        {
          ticket_id: ticket_id,
          sender: sender,
          recipient: recipient
        }
      end
      let(:metadata) do
        {
          correlation_id: correlation_id
        }
      end

      let(:ticket_transferred) do
        TransformationItem.new(
          event_id: event_id,
          data: data,
          metadata: metadata,
          event_type: 'RubyEventStore::Mappers::TicketTransferred'
        )
      end

      let(:ticket_cancelled) do
        TransformationItem.new(
          event_id: event_id,
          data: {
            ticket_id: ticket_id,
          },
          metadata: metadata,
          event_type: 'RubyEventStore::Mappers::TicketCancelled'
        )
      end

      def encrypt(e)
        mapper.dump(e)
      end

      def decrypt(r)
        mapper.load(r)
      end

      it_behaves_like :mapper, EncryptionMapper.new(InMemoryEncryptionKeyRepository.new), TicketCancelled.new

      specify 'decrypts encrypted fields in presence of encryption keys' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        event = decrypt(encrypt(ticket_transferred))

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: sender,
          recipient: recipient
        })
        expect(event.metadata).to eq(metadata)
      end

      specify 'obfuscates data for missing keys on decryption' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        record = encrypt(ticket_transferred)
        key_repository.forget(sender_id.dup)
        event  = decrypt(record)

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: {
            user_id: sender_id,
            name: ForgottenData::FORGOTTEN_DATA,
            email: ForgottenData::FORGOTTEN_DATA,
          },
          recipient: recipient
        })
        expect(event.metadata).to eq(metadata)
      end

      specify 'obfuscates data for incorrect keys on decryption' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        record = encrypt(ticket_transferred)
        key_repository.forget(sender_id)
        key_repository.create(sender_id)
        event  = decrypt(record)

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: {
            user_id: sender_id,
            name: ForgottenData::FORGOTTEN_DATA,
            email: ForgottenData::FORGOTTEN_DATA,
          },
          recipient: recipient
        })<commit_msg>Additional test case for nil value in encrypted attributes<commit_after>require 'spec_helper'
require 'openssl'
require 'ruby_event_store/spec/mapper_lint'

module RubyEventStore
  module Mappers
    TicketTransferred = Class.new(RubyEventStore::Event) do
      def self.encryption_schema
        {
          sender: {
            name:  ->(data) { data.dig(:sender, :user_id) },
            email: ->(data) { data.dig(:sender, :user_id) },
            twitter: ->(data) { data.dig(:sender, :user_id) },
          },
          recipient: {
            name:  ->(data) { data.dig(:recipient, :user_id) },
            email: ->(data) { data.dig(:recipient, :user_id) },
            twitter: ->(data) { data.dig(:sender, :user_id) },
          }
        }
      end
    end

    TicketCancelled = Class.new(RubyEventStore::Event)

    TicketHolderEmailProvided = Class.new(RubyEventStore::Event) do
      def self.encryption_schema
        {
          email: ->(data) do
            data.fetch(:user_id)
          end
        }
      end
    end

    RSpec.describe Encryption do
      let(:key_repository) { InMemoryEncryptionKeyRepository.new }
      let(:serializer)     { YAML }
      let(:mapper)         { Encryption.new(key_repository, serializer: serializer) }
      let(:sender_id)      { SecureRandom.uuid }
      let(:recipient_id)   { SecureRandom.uuid }
      let(:event_id)       { SecureRandom.uuid }
      let(:ticket_id)      { SecureRandom.uuid }
      let(:correlation_id) { SecureRandom.uuid }
      let(:sender_email)   { '<EMAIL>@universe' }
      let(:recipient) do
        {
          user_id: recipient_id,
          name: 'Bob',
          email: '<EMAIL>@universe',
          twitter: nil
        }
      end
      let(:sender) do
        {
          user_id: sender_id,
          name: 'Alice',
          email: sender_email,
          twitter: '@alice'
        }
      end
      let(:data) do
        {
          ticket_id: ticket_id,
          sender: sender,
          recipient: recipient
        }
      end
      let(:metadata) do
        {
          correlation_id: correlation_id
        }
      end

      let(:ticket_transferred) do
        TransformationItem.new(
          event_id: event_id,
          data: data,
          metadata: metadata,
          event_type: 'RubyEventStore::Mappers::TicketTransferred'
        )
      end

      let(:ticket_cancelled) do
        TransformationItem.new(
          event_id: event_id,
          data: {
            ticket_id: ticket_id,
          },
          metadata: metadata,
          event_type: 'RubyEventStore::Mappers::TicketCancelled'
        )
      end

      def encrypt(e)
        mapper.dump(e)
      end

      def decrypt(r)
        mapper.load(r)
      end

      it_behaves_like :mapper, EncryptionMapper.new(InMemoryEncryptionKeyRepository.new), TicketCancelled.new

      specify 'decrypts encrypted fields in presence of encryption keys' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        event = decrypt(encrypt(ticket_transferred))

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: sender,
          recipient: recipient
        })
        expect(event.metadata).to eq(metadata)
      end

      specify 'obfuscates data for missing keys on decryption' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        record = encrypt(ticket_transferred)
        key_repository.forget(sender_id.dup)
        event  = decrypt(record)

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: {
            user_id: sender_id,
            name: ForgottenData::FORGOTTEN_DATA,
            email: ForgottenData::FORGOTTEN_DATA,
            twitter: ForgottenData::FORGOTTEN_DATA,
          },
          recipient: recipient
        })
        expect(event.metadata).to eq(metadata)
      end

      specify 'obfuscates data for incorrect keys on decryption' do
        key_repository.create(sender_id)
        key_repository.create(recipient_id)

        record = encrypt(ticket_transferred)
        key_repository.forget(sender_id)
        key_repository.create(sender_id)
        event  = decrypt(record)

        expect(event.event_id).to eq(event_id)
        expect(event.data).to eq({
          ticket_id: ticket_id,
          sender: {
            user_id: sender_id,
            name: ForgottenData::FORGOTTEN_DATA,
            email: ForgottenData::FORGOTTEN_DATA,
            twitter: ForgottenData::FORGOTTEN_DATA,
          },
          recipient: recipient
        })
{'fraction_non_alphanumeric': 0.09633784229627186, 'fraction_numerical': 0.0, 'mean_word_length': 1.9516390782213566, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3306920', 'n_tokens_mistral': 2807, 'n_tokens_neox': 2699, 'n_words': 492}