require 'spec_helper'

describe BaseCRM::SyncService do
  let(:device_uuid) { '6dadcec8-6e61-4691-b318-1aab27b8fecf' }
  let(:session_id) { '29f2aeeb-8d68-4ea7-95c3-a2c8e151f5a3' }

  describe 'Responds to' do
    subject { BaseCRM::SyncService.new(double) }

    it { should respond_to :start }
    it { should respond_to :ack }
    it { should respond_to :fetch }
  end

  describe 'Client respond to' do
    subject { client }

    it { should respond_to :sync }
  end

  describe 'Client#sync' do
    it 'returns BaseCRM::SyncService instance' do
      expect(client.sync).to be_a BaseCRM::SyncService
    end
  end

  describe :start do
    describe 'validation' do
      context 'device_uuid is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.start(nil) }.to raise_error(ArgumentError)
        end
      end

      context 'device_uuid is empty' do
        it 'raises ArgumentError exception' do
          expect { client.sync.start(" ") }.to raise_error(ArgumentError)
        end
      end
    end

    context 'nothing new to fetch' do
      let(:http_response) do
        [204, {}, nil]
      end

      it 'returns nil' do
        expect(client.http_client).to receive(:post).with('/sync/start', {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
        expect(client.sync.start(device_uuid)).to be_nil
      end
    end

    context 'we have to data to synchronize' do
      let(:payload) do
        {
          data: {
            id: session_id,
            queues: [
              data: {
                name: 'main',
                pages: 1,
                total_count: 2
              },
              meta: {
                type: :sync_queue
              }
            ]
          },
          meta: {
            type: :sync_session
          }
        }
      end

      let(:http_response) do
        [201, {}, payload]
      end

      before :each do
        expect(client.http_client).to receive(:post).with('/sync/start', {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
      end

      it 'returns an instance of BaseCRM::SyncSession' do
        expect(client.sync.start(device_uuid)).to be_an BaseCRM::SyncSession
      end

      it "flattens BaseCRM::SyncSession's queues" do
        client.sync.start(device_uuid).queues.each do |queue|
          expect(queue).to be_an BaseCRM::SyncQueue
        end
      end
    end
  end

  describe :ack do
    let(:http_response) do
      [202, {}, nil]
    end

    let(:ack_keys) do
      ['User-1234-1', 'Source-1234-1']
    end

    describe 'validation' do
      context 'device_uuid is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.ack(nil, ack_keys) }.to raise_error(ArgumentError)
        end
      end

      context 'device_uuid is empty' do
        it 'raises ArgumentError exception' do
          expect { client.sync.ack(" ", ack_keys) }.to raise_error(ArgumentError)
        end
      end

      context 'ack_keys is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.ack(device_uuid, nil) }.to raise_error(ArgumentError)
        end
      end

      context 'ack_keys is not an Array' do
        it 'raises ArgumentError exception' do
          expect { client.sync.ack(device_uuid, {}) }.to raise_error(ArgumentError)
        end
      end
    end

    context 'empty ack_keys array' do
      let(:ack_keys) do
        []
      end

      it 'returns imedieatly with true value' do
        expect(client.http_client).not_to receive(:post)
        expect(client.sync.ack(device_uuid, ack_keys)).to eq(true)
      end
    end

    context 'non empty ack_keys call' do
      it 'returns true value' do
        expect(client.http_client).to receive(:post).with('/sync/ack', {ack_keys: ack_keys}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
        expect(client.sync.ack(device_uuid, ack_keys)).to eq(true)
      end
    end
  end

  describe :fetch do
    context 'validation' do
      context 'device_uuid is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(nil, session_id) }.to raise_error(ArgumentError)
        end
      end

      context 'device_uuid is empty' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(" ", session_id) }.to raise_error(ArgumentError)
        end
      end

      context 'session_id is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(device_uuid, nil) }.to raise_error(ArgumentError)
        end
      end

      context 'session_id is empty' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(device_uuid, ' ') }.to raise_error(ArgumentError)
        end
      end

      context 'queue names is nil' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(device_uuid, session_id, nil) }.to raise_error(ArgumentError)
        end
      end

      context 'queue names is empty' do
        it 'raises ArgumentError exception' do
          expect { client.sync.fetch(device_uuid, session_id, ' ') }.to raise_error(ArgumentError)
        end
      end
    end

    context 'no more data to fetch' do
      let(:http_response) do
        [204, {}, nil]
      end

      it 'returns an empty array' do
        expect(client.http_client).to receive(:get).with("/sync/#{session_id}/queues/main", {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
        expect(client.sync.fetch(device_uuid, session_id)).to eq([])
      end
    end

    context 'there is still data in the main queue' do
      let(:payload) do
        {
          items: [
            {
              data: {
                id: 1
              },
              meta: {
                type: 'user',
                sync: {
                  event_type: 'created',
                  ack_key: 'User-123-1',
                  revision: 1
                }
              }
            },
            {
              data: {
                id: 1
              },
              meta: {
                type: 'source',
                sync: {
                  event_type: 'created',
                  ack_key: 'Source-123-1',
                  revision: 1
                }
              }
            }
          ],
          meta: {
            type: 'collection',
            count: 2,
            count_left: 0
          }
        }
      end

      let(:http_response) do
        [200, {}, payload]
      end

      before :each do
        expect(client.http_client).to receive(:get).with("/sync/#{session_id}/queues/main", {}, {'X-Basecrm-Device-UUID' => device_uuid}).and_return(http_response)
      end

      it 'returns an array' do
        expect(client.sync.fetch(device_uuid, session_id)).to be_an Array
      end

      it 'returns a non empty array' do
        expect(client.sync.fetch(device_uuid, session_id)).not_to be_empty
      end

      it 'returns an array of two items' do
        expect(client.sync.fetch(device_uuid, session_id).length).to eq(2)
      end

      it 'returns an array of arrays' do
        client.sync.fetch(device_uuid, session_id).each do |item|
          expect(item).to be_an Array
          expect(item).not_to be_empty
          expect(item.length).to eq(2)
        end
      end

      it 'returns an array where the first element is BaseCRM::SyncMeta and the second is a model' do
        items = client.sync.fetch(device_uuid, session_id)

        sync_meta, user = items[0]
        expect(sync_meta).to be_a BaseCRM::SyncMeta
        expect(user).to be_a BaseCRM::User
        expect(sync_meta.ack_key).to eq('User-123-1')
        expect(user.id).to eq(1)

        sync_meta, source = items[1]
        expect(sync_meta).to be_a BaseCRM::SyncMeta
        expect(source).to be_a BaseCRM::Source
        expect(sync_meta.ack_key).to eq('Source-123-1')
        expect(source.id).to eq(1)
      end
    end
  end
end