require 'spec_helper'
require 'flapjack/data/entity'

describe Flapjack::Data::Entity, :redis => true do

  let(:name)  { 'abc-123' }
  let(:check) { 'ping' }

  it "creates an entity if allowed when it can't find it" do
    entity = Flapjack::Data::Entity.find_by_name(name, :redis => @redis, :create => true)

    expect(entity).not_to be_nil
    expect(entity.name).to eq(name)
    expect(@redis.hget('all_entity_ids_by_name', name)).to match(/^\S+$/)
  end

  it 'creates tags if passed when added' do
    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name,
                                'tags' => ['database', 'virtual']},
                                :redis => @redis)

    entity = Flapjack::Data::Entity.find_by_id('5000', :redis => @redis)
    expect(entity).not_to be_nil
    expect(entity.tags).to eq(Set.new(['database', 'virtual']))
  end

  it "adds a registered contact with an entity" do
    Flapjack::Data::Contact.add({'id'         => '362',
                                 'first_name' => 'John',
                                 'last_name'  => 'Johnson',
                                 'email'      => 'johnj@example.com' },
                                 :redis       => @redis)

    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name,
                                'contacts' => ['362']},
                                :redis => @redis)

    entity = Flapjack::Data::Entity.find_by_id('5000', :redis => @redis)
    expect(entity).not_to be_nil
    contacts = entity.contacts
    expect(contacts).not_to be_nil
    expect(contacts).to be_an(Array)
    expect(contacts.size).to eq(1)
    expect(contacts.first.name).to eq('John Johnson')
  end

  it "does not add a registered contact with an entity if the contact is unknown" do
    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name,
                                'contacts' => ['362']},
                                :redis => @redis)

    entity = Flapjack::Data::Entity.find_by_id('5000', :redis => @redis)
    expect(entity).not_to be_nil
    contacts = entity.contacts
    expect(contacts).not_to be_nil
    expect(contacts).to be_an(Array)
    expect(contacts).to be_empty
  end

  it "finds an entity by id" do
    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name},
                                :redis => @redis)

    entity = Flapjack::Data::Entity.find_by_id('5000', :redis => @redis)
    expect(entity).not_to be_nil
    expect(entity.name).to eq(name)
  end

  it "finds an entity by name" do
    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name},
                                :redis => @redis)

    entity = Flapjack::Data::Entity.find_by_name(name, :redis => @redis)
    expect(entity).not_to be_nil
    expect(entity.id).to eq('5000')
  end

  it "returns an empty list when asked for all entities, if there are no entities" do
    entities = Flapjack::Data::Entity.all(:redis => @redis)
    expect(entities).not_to be_nil
    expect(entities).to be_an(Array)
    expect(entities.size).to eq(0)
  end

  it "returns a list of all entities" do
    Flapjack::Data::Entity.add({'id'   => '5000',
                                'name' => name},
                                :redis => @redis)
    Flapjack::Data::Entity.add({'id'   => '5001',
                                'name' => "z_" + name},
                                :redis => @redis)

    entities = Flapjack::Data::Entity.all(:redis => @redis)
    expect(entities).not_to be_nil
    expect(entities).to be_an(Array)
    expect(entities.size).to eq(2)
    expect(entities[0].id).to eq('5000')
    expect(entities[1].id).to eq('5001')
  end

  it "returns a list of checks for an entity" do
    Flapjack::Data::Entity.add({'id'       => '5000',
                                'name'     => name},
                                :redis => @redis)

    @redis.zadd("current_checks:#{name}", Time.now.to_i, "ping")
    @redis.zadd("current_checks:#{name}", Time.now.to_i, "ssh")

    entity = Flapjack::Data::Entity.find_by_name(name, :redis => @redis)
    check_list = entity.check_list
    expect(check_list).not_to be_nil
    expect(check_list.size).to eq(2)
    sorted_list = check_list.sort
    expect(sorted_list[0]).to eq('ping')
    expect(sorted_list[1]).to eq('ssh')
  end

  it "returns a count of checks for an entity" do
    Flapjack::Data::Entity.add({'id'       => '5000',
                                'name'     => name,
                                'contacts' => []},
                               :redis => @redis)

    @redis.zadd("current_checks:#{name}", Time.now.to_i, "ping")
    @redis.zadd("current_checks:#{name}", Time.now.to_i, "ssh")

    entity = Flapjack::Data::Entity.find_by_id(5000, :redis => @redis)
    check_count = entity.check_count
    expect(check_count).not_to be_nil
    expect(check_count).to eq(2)
  end

  it "finds entity names matching a pattern" do
    Flapjack::Data::Entity.add({'id'       => '5000',
                                'name'     => 'abc-123',
                                'contacts' => []},
                               :redis => @redis)

    Flapjack::Data::Entity.add({'id'       => '5001',
                                'name'     => 'def-456',
                                'contacts' => []},
                               :redis => @redis)

    entities = Flapjack::Data::Entity.find_all_name_matching('abc', :redis => @redis)
    expect(entities).not_to be_nil
    expect(entities).to be_an(Array)
    expect(entities.size).to eq(1)
    expect(entities.first).to eq('abc-123')
  end

  it "adds tags to entities" do
    entity = Flapjack::Data::Entity.add({'id'       => '5000',
                                         'name'     => 'abc-123',
                                         'contacts' => []},
                                        :redis => @redis)

    entity.add_tags('source:foobar', 'foo')

    expect(entity).not_to be_nil
    expect(entity).to be_an(Flapjack::Data::Entity)
    expect(entity.name).to eq('abc-123')
    expect(entity.tags).to include("source:foobar")
    expect(entity.tags).to include("foo")

    # and test the tags as read back from redis
    entity = Flapjack::Data::Entity.find_by_id('5000', :redis => @redis)
    expect(entity.tags).to include("source:foobar")
    expect(entity.tags).to include("foo")

  end

  it "deletes tags from entities" do
    entity = Flapjack::Data::Entity.add({'id'       => '5000',
                                         'name'     => 'abc-123',
                                         'contacts' => []},
                                        :redis => @redis)

    entity.add_tags('source:foobar', 'foo')

    expect(entity).not_to be_nil
    expect(entity.tags).to include("source:foobar")
    expect(entity.tags).to include("foo")

    entity.delete_tags('source:foobar')

    expect(entity.tags).not_to include("source:foobar")
    expect(entity.tags).to include("foo")

  end

  context 'renaming and merging' do

    let(:time_i) { Time.now.to_i }

    let(:sha1) { Digest::SHA1.new }
    let(:hash_name1) { Digest.hexencode(sha1.digest('name1:PING'))[0..7].downcase }
    let(:hash_name2) { Digest.hexencode(sha1.digest('name2:PING'))[0..7].downcase }

    def add_name1
      Flapjack::Data::Entity.add({'id'   => '5000',
                                  'name' => 'name1',
                                  'contacts' => ['362']},
                                  :redis => @redis)
    end

    def add_name2
      Flapjack::Data::Entity.add({'id'   => '5000',
                                  'name' => 'name2',
                                  'contacts' => ['362']},
                                  :redis => @redis)
    end

    before(:each) do
      Flapjack::Data::Contact.add({'id'         => '362',
                                   'first_name' => 'John',
                                   'last_name'  => 'Johnson',
                                   'email'      => 'johnj@example.com' },
                                   :redis       => @redis)
    end

    context 'check that rename and merge are called by add'

    context 'entity renaming on #add' do

      let(:time_i) { Time.now.to_i }

      let(:sha1) { Digest::SHA1.new }
      let(:hash_name1) { Digest.hexencode(sha1.digest('name1:PING'))[0..7].downcase }
      let(:hash_name2) { Digest.hexencode(sha1.digest('name2:PING'))[0..7].downcase }

      before(:each) do
        add_name1
      end

      it 'renames the "entity name to id lookup" and the name in the "entity hash by id"' do
        expect(@redis.hget('all_entity_ids_by_name', 'name1')).to eq('5000')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to eq('name1')

        add_name2

        expect(@redis.hget('all_entity_ids_by_name', 'name1')).to be_nil
        expect(@redis.hget('all_entity_ids_by_name', 'name2')).to eq('5000')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to eq('name2')
      end

      it 'does not rename an entity if an entity with the new name already exists' do
        Flapjack::Data::Entity.add({'id'   => '5001',
                                    'name' => 'name2',
                                    'contacts' => []},
                                    :redis => @redis)

        expect(@redis.hget('all_entity_ids_by_name', 'name1')).to eq('5000')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to eq('name1')
        expect(@redis.hget('all_entity_ids_by_name', 'name2')).to eq('5001')
        expect(@redis.hget('all_entity_names_by_id', '5001')).to eq('name2')

        add_name2

        # no change
        expect(@redis.hget('all_entity_ids_by_name', 'name1')).to eq('5000')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to eq('name1')
        expect(@redis.hget('all_entity_ids_by_name', 'name2')).to eq('5001')
        expect(@redis.hget('all_entity_names_by_id', '5001')).to eq('name2')
      end

      it 'renames current check state' do
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)

        add_name2

        expect(@redis.hgetall('check:name1:PING')).to eq({})
        expect(@redis.hgetall('check:name2:PING')).to eq(data)
      end

      it 'renames stored check state changes' do
        @redis.rpush('name1:PING:states', time_i)
        @redis.set("name1:PING:#{time_i}:state", 'critical')
        @redis.set("name1:PING:#{time_i}:summary", 'bad')
        @redis.zadd('name1:PING:sorted_state_timestamps', time_i, time_i)

        add_name2

        expect(@redis.type('name1:PING:states')).to eq('none')
        expect(@redis.type('name2:PING:states')).to eq('list')
        expect(@redis.lindex('name2:PING:states', 0)).to eq(time_i.to_s)
        expect(@redis.get("name1:PING:#{time_i}:state")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:state")).to eq('critical')
        expect(@redis.get("name1:PING:#{time_i}:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:summary")).to eq('bad')
        expect(@redis.zrange("name1:PING:sorted_state_timestamps", 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange("name2:PING:sorted_state_timestamps", 0, -1, :with_scores => true)).to eq([[time_i.to_s, time_i.to_f]])
      end

      it 'renames stored action events' do
        @redis.hset('name1:PING:actions', time_i.to_s, 'acknowledgement')

        add_name2

        expect(@redis.hget('name1:PING:actions', time_i.to_s)).to be_nil
        expect(@redis.hget('name2:PING:actions', time_i.to_s)).to eq('acknowledgement')
      end

      it 'renames entries in the sorted set of failing checks' do
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)

        @redis.zadd('failed_checks', time_i, 'name1:PING')

        add_name2

        expect(@redis.zrange('failed_checks', 0, -1, :with_scores => true)).to eq([['name2:PING', time_i.to_f]])
      end

      it 'renames the list of current checks, and its entries' do
        @redis.zadd('current_checks:name1', time_i, 'PING')

        add_name2

        expect(@redis.zrange('current_checks:name1', 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange('current_checks:name2', 0, -1, :with_scores => true)).to eq([['PING', time_i.to_f]])
      end

      it 'renames an entry in the list of current entities' do
        @redis.zadd('current_entities', time_i, 'name1')

        add_name2

        expect(@redis.zrange('current_entities', 0, -1, :with_scores => true)).to eq([['name2', time_i.to_f]])
      end

      it 'renames a current unscheduled maintenance key' do
        @redis.setex('name1:PING:unscheduled_maintenance', 30, time_i)

        add_name2

        expect(@redis.get('name1:PING:unscheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:unscheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:unscheduled_maintenance')).to be <= 30
      end

      it 'renames stored unscheduled maintenance periods and sorted timestamps' do
        @redis.zadd('name1:PING:unscheduled_maintenances', 30, time_i)
        @redis.set("name1:PING:#{time_i}:unscheduled_maintenance:summary", 'really bad')
        @redis.zadd('name1:PING:sorted_unscheduled_maintenance_timestamps', time_i, time_i)

        add_name2

        expect(@redis.zrange('name1:PING:unscheduled_maintenances', 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange('name2:PING:unscheduled_maintenances', 0, -1, :with_scores => true)).to eq([[time_i.to_s, 30.0]])
        expect(@redis.get("name1:PING:#{time_i}:unscheduled_maintenance:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:unscheduled_maintenance:summary")).to eq('really bad')
        expect(@redis.zrange('name1:PING:sorted_unscheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange('name2:PING:sorted_unscheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq([[time_i.to_s, time_i.to_f]])
      end

      it 'renames a current scheduled maintenance key' do
        @redis.setex('name1:PING:scheduled_maintenance', 30, time_i)

        add_name2

        expect(@redis.get('name1:PING:scheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:scheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:scheduled_maintenance')).to be <= 30
      end

      it 'renames stored scheduled maintenance periods and sorted timestamps' do
        @redis.zadd('name1:PING:scheduled_maintenances', 30, time_i)
        @redis.set("name1:PING:#{time_i}:scheduled_maintenance:summary", 'really bad')
        @redis.zadd('name1:PING:sorted_scheduled_maintenance_timestamps', time_i, time_i)

        add_name2

        expect(@redis.zrange('name1:PING:scheduled_maintenances', 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange('name2:PING:scheduled_maintenances', 0, -1, :with_scores => true)).to eq([[time_i.to_s, 30.0]])
        expect(@redis.get("name1:PING:#{time_i}:scheduled_maintenance:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:scheduled_maintenance:summary")).to eq('really bad')
        expect(@redis.zrange('name1:PING:sorted_scheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq([])
        expect(@redis.zrange('name2:PING:sorted_scheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq([[time_i.to_s, time_i.to_f]])
      end

      it 'renames current notifications' do
        @redis.set('name1:PING:last_problem_notification',         time_i)
        @redis.set('name1:PING:last_unknown_notification',         time_i - 100)
        @redis.set('name1:PING:last_warning_notification',         time_i - 50)
        @redis.set('name1:PING:last_critical_notification',        time_i)
        @redis.set('name1:PING:last_recovery_notification',        time_i - 200)
        @redis.set('name1:PING:last_acknowledgement_notification', time_i - 250)

        add_name2

        expect(@redis.get('name1:PING:last_problem_notification')).to be_nil
        expect(@redis.get('name1:PING:last_unknown_notification')).to be_nil
        expect(@redis.get('name1:PING:last_warning_notification')).to be_nil
        expect(@redis.get('name1:PING:last_critical_notification')).to be_nil
        expect(@redis.get('name1:PING:last_recovery_notification')).to be_nil
        expect(@redis.get('name1:PING:last_acknowledgement_notification')).to be_nil
        expect(@redis.get('name2:PING:last_problem_notification')).to eq(time_i.to_s)
        expect(@redis.get('name2:PING:last_unknown_notification')).to eq((time_i - 100).to_s)
        expect(@redis.get('name2:PING:last_warning_notification')).to eq((time_i - 50).to_s)
        expect(@redis.get('name2:PING:last_critical_notification')).to eq(time_i.to_s)
        expect(@redis.get('name2:PING:last_recovery_notification')).to eq((time_i - 200).to_s)
        expect(@redis.get('name2:PING:last_acknowledgement_notification')).to eq((time_i - 250).to_s)
      end

      it 'renames stored notifications' do
        @redis.lpush('name1:PING:problem_notifications', time_i)
        @redis.lpush('name1:PING:unknown_notifications', time_i - 100)
        @redis.lpush('name1:PING:warning_notifications', time_i - 50)
        @redis.lpush('name1:PING:critical_notifications', time_i)
        @redis.lpush('name1:PING:recovery_notifications', time_i - 200)
        @redis.lpush('name1:PING:acknowledgement_notifications', time_i - 250)

        add_name2

        expect(@redis.llen('name1:PING:problem_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:unknown_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:warning_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:critical_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:recovery_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:acknowledgement_notifications')).to eq(0)
        expect(@redis.lindex('name2:PING:problem_notifications', 0)).to eq(time_i.to_s)
        expect(@redis.lindex('name2:PING:unknown_notifications', 0)).to eq((time_i - 100).to_s)
        expect(@redis.lindex('name2:PING:warning_notifications', 0)).to eq((time_i - 50).to_s)
        expect(@redis.lindex('name2:PING:critical_notifications', 0)).to eq(time_i.to_s)
        expect(@redis.lindex('name2:PING:recovery_notifications', 0)).to eq((time_i - 200).to_s)
        expect(@redis.lindex('name2:PING:acknowledgement_notifications', 0)).to eq((time_i - 250).to_s)
      end

      it 'renames alert blocks' do
        @redis.setex('drop_alerts_for_contact:362:email:name1:PING:critical', 30, 30)

        add_name2

        expect(@redis.get('drop_alerts_for_contact:362:email:name1:PING:critical')).to be_nil
        expect(@redis.get('drop_alerts_for_contact:362:email:name2:PING:critical')).to eq(30.to_s)
        expect(@redis.ttl('drop_alerts_for_contact:362:email:name2:PING:critical')).to be <= 30
      end

      it "updates the check hash set" do
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)

        @redis.hset('checks_by_hash', hash_name1, 'name1:PING')

        add_name2

        expect(@redis.hget('checks_by_hash', hash_name1)).to be_nil
        expect(@redis.hget('checks_by_hash', hash_name2)).to eq('name2:PING')
      end

      it 'renames entries within alerting checks' do
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)

        @redis.zadd('contact_alerting_checks:362:media:email', time_i, 'name1:PING')

        add_name2

        expect(@redis.zrange('contact_alerting_checks:362:media:email', 0, -1, :with_scores => true)).to eq(
          [['name2:PING', time_i.to_f]]
        )
      end

    end

    context 'entity merging' do

      let(:time_2_i) { time_i + 30 }

      def do_merge
        Flapjack::Data::Entity.merge('name1', 'name2', :redis => @redis)
      end

      # used as basic existence check for state on check on original entity
      def add_state1
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)
      end

      def add_state2
        data = {'state' => 'critical', 'last_change' => time_2_i.to_s, 'last_update' => time_2_i.to_s}
        @redis.mapped_hmset('check:name2:PING', data)
      end

      before(:each) do
        add_name2
      end

      it 'clears the old id when a name exists' do
        expect(@redis.hget('all_entity_ids_by_name', 'name2')).to eq('5000')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to eq('name2')

        Flapjack::Data::Entity.add({'id'   => '5001',
                                    'name' => 'name2'},
                                    :redis => @redis)

        expect(@redis.hget('all_entity_ids_by_name', 'name2')).to eq('5001')
        expect(@redis.hget('all_entity_names_by_id', '5000')).to be_nil
        expect(@redis.hget('all_entity_names_by_id', '5001')).to eq('name2')
      end

      it 'does not overwrite current state for a check' do
        add_state1
        data_2 = {'state' => 'critical', 'last_change' => time_2_i.to_s, 'last_update' => time_2_i.to_s}
        @redis.mapped_hmset('check:name2:PING', data_2)

        do_merge

        expect(@redis.hgetall('check:name1:PING')).to eq({})
        expect(@redis.hgetall('check:name2:PING')).to eq(data_2)
      end

      it 'sets current state for a check if no new state is set' do
        data = {'state' => 'critical', 'last_change' => time_i.to_s, 'last_update' => time_i.to_s}
        @redis.mapped_hmset('check:name1:PING', data)

        do_merge

        expect(@redis.hgetall('check:name1:PING')).to eq({})
        expect(@redis.hgetall('check:name2:PING')).to eq(data)
      end

      it 'merges stored check state changes' do
        add_state1
        add_state2

        @redis.rpush('name1:PING:states', time_i)
        @redis.set("name1:PING:#{time_i}:state", 'critical')
        @redis.set("name1:PING:#{time_i}:summary", 'bad')
        @redis.zadd('name1:PING:sorted_state_timestamps', time_i, time_i)

        @redis.rpush('name2:PING:states', time_2_i)
        @redis.set("name2:PING:#{time_2_i}:state", 'ok')
        @redis.set("name2:PING:#{time_2_i}:summary", 'good')
        @redis.zadd('name2:PING:sorted_state_timestamps', time_2_i, time_2_i)

        do_merge

        expect(@redis.type('name1:PING:states')).to eq('none')
        expect(@redis.type('name2:PING:states')).to eq('list')

        expect(@redis.llen('name2:PING:states')).to eq(2)
        expect(@redis.lindex('name2:PING:states', 0)).to eq(time_i.to_s)
        expect(@redis.lindex('name2:PING:states', 1)).to eq(time_2_i.to_s)

        expect(@redis.get("name1:PING:#{time_i}:state")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:state")).to eq('critical')
        expect(@redis.get("name2:PING:#{time_2_i}:state")).to eq('ok')

        expect(@redis.get("name1:PING:#{time_i}:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:summary")).to eq('bad')
        expect(@redis.get("name2:PING:#{time_2_i}:summary")).to eq('good')

        expect(@redis.zrange("name1:PING:sorted_state_timestamps", 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange("name2:PING:sorted_state_timestamps", 0, -1, :with_scores => true)).to eq(
          [[time_i.to_s, time_i.to_f], [time_2_i.to_s, time_2_i.to_f]])
      end

      it 'merges stored action events' do
        @redis.hset('name1:PING:actions', time_i, 'acknowledgement')
        @redis.hset('name2:PING:actions', time_2_i, 'test_notifications')

        do_merge

        expect(@redis.hget('name1:PING:actions', time_i.to_s)).to be_nil
        expect(@redis.hget('name2:PING:actions', time_i.to_s)).to eq('acknowledgement')
        expect(@redis.hget('name2:PING:actions', time_2_i.to_s)).to eq('test_notifications')
      end

      it 'does not overwrite an entry in the sorted set of failing checks' do
        add_state1
        add_state2

        @redis.zadd('failed_checks', time_i,   'name1:PING')
        @redis.zadd('failed_checks', time_2_i, 'name2:PING')

        do_merge

        expect(@redis.zrange('failed_checks', 0, -1, :with_scores => true)).to eq([['name2:PING', time_2_i.to_f]])
      end

      it 'merges an entry in the sorted set of failing checks (if no new state data)' do
        add_state1

        @redis.zadd('failed_checks', time_i, 'name1:PING')

        do_merge

        expect(@redis.zrange('failed_checks', 0, -1, :with_scores => true)).to eq([['name2:PING', time_i.to_f]])
      end

      it 'merges the list of current checks, and its entries' do
        @redis.zadd('current_checks:name1', time_i, 'PING')
        @redis.zadd('current_checks:name2', time_2_i, 'SSH')

        do_merge

        expect(@redis.zrange('current_checks:name1', 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange('current_checks:name2', 0, -1, :with_scores => true)).to eq(
          [['PING', time_i.to_f], ['SSH', time_2_i.to_f]])
      end

      it 'removes an entry from the list of current entities' do
        @redis.zadd('current_entities', time_i, 'name1')
        @redis.zadd('current_entities', time_2_i, 'name2')

        do_merge

        expect(@redis.zrange('current_entities', 0, -1, :with_scores => true)).to eq([['name2', time_2_i.to_f]])
      end

      it 'moves an entry within the list of current entities' do
        @redis.zadd('current_entities', time_i, 'name1')

        do_merge

        expect(@redis.zrange('current_entities', 0, -1, :with_scores => true)).to eq([['name2', time_i.to_f]])
      end

      it "renames an unscheduled maintenance key" do
        @redis.setex('name1:PING:unscheduled_maintenance', 30, time_i)
        @redis.zadd('name1:PING:unscheduled_maintenances', 30, time_i)

        do_merge

        expect(@redis.get('name1:PING:unscheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:unscheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:unscheduled_maintenance')).to be <= 30
      end

      it 'merges an unscheduled maintenance key (older has longest to live)' do
        @redis.setex('name1:PING:unscheduled_maintenance', 60, time_i)
        @redis.zadd('name1:PING:unscheduled_maintenances', 60, time_i)
        @redis.setex('name2:PING:unscheduled_maintenance', 20, time_2_i)
        @redis.zadd('name2:PING:unscheduled_maintenances', 20, time_2_i)

        do_merge

        expect(@redis.get('name1:PING:unscheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:unscheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:unscheduled_maintenance')).to be <= 60
      end

      it 'merges an unscheduled maintenance key (newer has longest to live)' do
        @redis.setex('name1:PING:unscheduled_maintenance', 40, time_i)
        @redis.zadd('name1:PING:unscheduled_maintenances', 40, time_i)
        @redis.setex('name2:PING:unscheduled_maintenance', 20, time_2_i)
        @redis.zadd('name2:PING:unscheduled_maintenances', 20, time_i)

        do_merge

        expect(@redis.get('name1:PING:unscheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:unscheduled_maintenance')).to eq(time_2_i.to_s)
        expect(@redis.ttl('name2:PING:unscheduled_maintenance')).to be <= 20
      end

      it 'merges stored unscheduled maintenance periods and sorted timestamps' do
        @redis.zadd('name1:PING:unscheduled_maintenances', 30, time_i)
        @redis.set("name1:PING:#{time_i}:unscheduled_maintenance:summary", 'really bad')
        @redis.zadd('name1:PING:sorted_unscheduled_maintenance_timestamps', time_i, time_i)

        @redis.zadd('name2:PING:unscheduled_maintenances', 20, time_2_i)
        @redis.set("name2:PING:#{time_2_i}:unscheduled_maintenance:summary", 'not too bad')
        @redis.zadd('name2:PING:sorted_unscheduled_maintenance_timestamps', time_2_i, time_2_i)

        do_merge

        expect(@redis.zrange('name1:PING:unscheduled_maintenances', 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange('name2:PING:unscheduled_maintenances', 0, -1, :with_scores => true)).to eq(
          [[time_2_i.to_s, 20.0], [time_i.to_s, 30.0]])
        expect(@redis.get("name1:PING:#{time_i}:unscheduled_maintenance:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:unscheduled_maintenance:summary")).to eq('really bad')
        expect(@redis.get("name2:PING:#{time_2_i}:unscheduled_maintenance:summary")).to eq('not too bad')
        expect(@redis.zrange('name1:PING:sorted_unscheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange('name2:PING:sorted_unscheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq(
          [[time_i.to_s, time_i.to_f], [time_2_i.to_s, time_2_i.to_f]])
      end

      it "renames a scheduled maintenance key" do
        @redis.setex('name1:PING:scheduled_maintenance', 30, time_i)

        do_merge

        expect(@redis.get('name1:PING:scheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:scheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:scheduled_maintenance')).to be <= 30
      end

      it 'merges a scheduled maintenance key (older has longer to live)' do
        @redis.setex('name1:PING:scheduled_maintenance', 60, time_i)
        @redis.setex('name2:PING:scheduled_maintenance', 20, time_2_i)

        do_merge

        expect(@redis.get('name1:PING:scheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:scheduled_maintenance')).to eq(time_i.to_s)
        expect(@redis.ttl('name2:PING:scheduled_maintenance')).to be <= 60
      end

      it 'merges a scheduled maintenance key (newer has longer to live)' do
        @redis.setex('name1:PING:scheduled_maintenance', 40, time_i)
        @redis.setex('name2:PING:scheduled_maintenance', 20, time_2_i)

        do_merge

        expect(@redis.get('name1:PING:scheduled_maintenance')).to be_nil
        expect(@redis.get('name2:PING:scheduled_maintenance')).to eq(time_2_i.to_s)
        expect(@redis.ttl('name2:PING:scheduled_maintenance')).to be <= 20
      end

      it 'merges stored scheduled maintenance periods and sorted timestamps' do
        @redis.zadd('name1:PING:scheduled_maintenances', 30, time_i)
        @redis.set("name1:PING:#{time_i}:scheduled_maintenance:summary", 'really bad')
        @redis.zadd('name1:PING:sorted_scheduled_maintenance_timestamps', time_i, time_i)

        @redis.zadd('name2:PING:scheduled_maintenances', 20, time_2_i)
        @redis.set("name2:PING:#{time_2_i}:scheduled_maintenance:summary", 'not too bad')
        @redis.zadd('name2:PING:sorted_scheduled_maintenance_timestamps', time_2_i, time_2_i)

        do_merge

        expect(@redis.zrange('name1:PING:scheduled_maintenances', 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange('name2:PING:scheduled_maintenances', 0, -1, :with_scores => true)).to eq(
          [[time_2_i.to_s, 20.0], [time_i.to_s, 30.0]])
        expect(@redis.get("name1:PING:#{time_i}:scheduled_maintenance:summary")).to be_nil
        expect(@redis.get("name2:PING:#{time_i}:scheduled_maintenance:summary")).to eq('really bad')
        expect(@redis.get("name2:PING:#{time_2_i}:scheduled_maintenance:summary")).to eq('not too bad')
        expect(@redis.zrange('name1:PING:sorted_scheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq(
          [])
        expect(@redis.zrange('name2:PING:sorted_scheduled_maintenance_timestamps', 0, -1, :with_scores => true)).to eq(
          [[time_i.to_s, time_i.to_f], [time_2_i.to_s, time_2_i.to_f]])
      end

      it 'merges current notifications, using old timestamp if no new one' do
        @redis.set('name1:PING:last_problem_notification',         time_i)
        @redis.set('name1:PING:last_unknown_notification',         time_i - 100)
        @redis.set('name1:PING:last_warning_notification',         time_i - 50)
        @redis.set('name1:PING:last_critical_notification',        time_i)
        @redis.set('name1:PING:last_recovery_notification',        time_i - 200)
        @redis.set('name1:PING:last_acknowledgement_notification', time_i - 250)

        @redis.set('name2:PING:last_problem_notification',         time_i + 800)
        @redis.set('name2:PING:last_critical_notification',        time_i + 800)

        do_merge

        expect(@redis.get('name1:PING:last_problem_notification')).to be_nil
        expect(@redis.get('name1:PING:last_unknown_notification')).to be_nil
        expect(@redis.get('name1:PING:last_warning_notification')).to be_nil
        expect(@redis.get('name1:PING:last_critical_notification')).to be_nil
        expect(@redis.get('name1:PING:last_recovery_notification')).to be_nil
        expect(@redis.get('name1:PING:last_acknowledgement_notification')).to be_nil

        expect(@redis.get('name2:PING:last_problem_notification')).to eq((time_i + 800).to_s)
        expect(@redis.get('name2:PING:last_unknown_notification')).to eq((time_i - 100).to_s)
        expect(@redis.get('name2:PING:last_warning_notification')).to eq((time_i - 50).to_s)
        expect(@redis.get('name2:PING:last_critical_notification')).to eq((time_i + 800).to_s)
        expect(@redis.get('name2:PING:last_recovery_notification')).to eq((time_i - 200).to_s)
        expect(@redis.get('name2:PING:last_acknowledgement_notification')).to eq((time_i - 250).to_s)
      end

      it 'merges stored notifications' do
        add_state1

        @redis.lpush('name1:PING:problem_notifications', time_i)
        @redis.lpush('name1:PING:unknown_notifications', time_i - 100)
        @redis.lpush('name1:PING:warning_notifications', time_i - 50)
        @redis.lpush('name1:PING:critical_notifications', time_i)
        @redis.lpush('name1:PING:recovery_notifications', time_i - 200)
        @redis.lpush('name1:PING:acknowledgement_notifications', time_i - 250)

        @redis.lpush('name2:PING:problem_notifications', time_i + 800)
        @redis.lpush('name2:PING:unknown_notifications', time_i + 800)

        do_merge

        expect(@redis.llen('name1:PING:problem_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:unknown_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:warning_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:critical_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:recovery_notifications')).to eq(0)
        expect(@redis.llen('name1:PING:acknowledgement_notifications')).to eq(0)
        expect(@redis.lindex('name2:PING:problem_notifications', 0)).to eq(time_i.to_s)
        expect(@redis.lindex('name2:PING:problem_notifications', 1)).to eq((time_i + 800).to_s)
        expect(@redis.lindex('name2:PING:unknown_notifications', 0)).to eq((time_i - 100).to_s)
        expect(@redis.lindex('name2:PING:unknown_notifications', 1)).to eq((time_i + 800).to_s)
        expect(@redis.lindex('name2:PING:warning_notifications', 0)).to eq((time_i - 50).to_s)
        expect(@redis.lindex('name2:PING:critical_notifications', 0)).to eq(time_i.to_s)
        expect(@redis.lindex('name2:PING:recovery_notifications', 0)).to eq((time_i - 200).to_s)
        expect(@redis.lindex('name2:PING:acknowledgement_notifications', 0)).to eq((time_i - 250).to_s)
      end

      it 'merges alerting checks (no notification state for new)' do
        add_state1

        @redis.zadd('contact_alerting_checks:362:media:email', time_i, 'name1:PING')

        @redis.lpush('name1:PING:problem_notifications', time_i)
        @redis.lpush('name1:PING:critical_notifications', time_i)

        do_merge

        expect(@redis.zrange('contact_alerting_checks:362:media:email', 0, -1, :with_scores => true)).to eq(
          [['name2:PING', time_i.to_f]])
      end

      it 'merges alerting checks (existing notification state for new)' do
        add_state1

        @redis.zadd('contact_alerting_checks:362:media:email', time_i, 'name1:PING')

        @redis.lpush('name1:PING:problem_notifications', time_i)
        @redis.lpush('name1:PING:critical_notifications', time_i)

        @redis.lpush('name2:PING:problem_notifications', time_i + 100)
        @redis.lpush('name2:PING:critical_notifications', time_i + 100)
        @redis.lpush('name2:PING:recovery_notifications', time_i + 150)

        do_merge

        expect(@redis.zrange('contact_alerting_checks:362:media:email', 0, -1, :with_scores => true)).to eq(
          [])
      end

      it 'merges alert blocks (no notification state for new)' do
        @redis.setex('drop_alerts_for_contact:362:email:name1:PING:critical', 30, 30)

        do_merge

        expect(@redis.get('drop_alerts_for_contact:362:email:name1:PING:critical')).to be_nil
        expect(@redis.get('drop_alerts_for_contact:362:email:name2:PING:critical')).to eq(30.to_s)
        expect(@redis.ttl('drop_alerts_for_contact:362:email:name2:PING:critical')).to be <= 30
      end

      it 'merges alert blocks (older has longer to run)' do
        @redis.setex('drop_alerts_for_contact:362:email:name1:PING:critical', 30, 30)
        @redis.setex('drop_alerts_for_contact:362:email:name2:PING:critical', 10, 10)

        do_merge

        expect(@redis.get('drop_alerts_for_contact:362:email:name1:PING:critical')).to be_nil
        expect(@redis.get('drop_alerts_for_contact:362:email:name2:PING:critical')).to eq(30.to_s)
        expect(@redis.ttl('drop_alerts_for_contact:362:email:name2:PING:critical')).to be <= 30
      end

      it 'merges alert blocks (newer has longer to run)' do
        @redis.setex('drop_alerts_for_contact:362:email:name1:PING:critical', 30, 30)
        @redis.setex('drop_alerts_for_contact:362:email:name2:PING:critical', 60, 60)

        do_merge

        expect(@redis.get('drop_alerts_for_contact:362:email:name1:PING:critical')).to be_nil
        expect(@redis.get('drop_alerts_for_contact:362:email:name2:PING:critical')).to eq(60.to_s)
        expect(@redis.ttl('drop_alerts_for_contact:362:email:name2:PING:critical')).to be <= 60
      end

    end

  end

end