require 'spec_helper'

describe Socialization::RedisStores::Base do
  # Testing through RedisStores::Follow for easy testing
  before(:each) do
    use_redis_store
    @klass = Socialization::RedisStores::Follow
    @klass.touch nil
    @klass.after_follow nil
    @klass.after_unfollow nil
    @follower1 = ImAFollower.create
    @follower2 = ImAFollower.create
    @followable1 = ImAFollowable.create
    @followable2 = ImAFollowable.create
  end

  describe "RedisStores::Base through RedisStores::Follow" do
    describe "Stores" do
      it "inherits Socialization::RedisStores::Follow" do
        expect(Socialization.follow_model).to eq(Socialization::RedisStores::Follow)
      end
    end

    describe "#follow!" do
      it "creates follow records" do
        @klass.follow!(@follower1, @followable1)
        expect(Socialization.redis.smembers(forward_key(@followable1))).to match_array ["#{@follower1.class}:#{@follower1.id}"]
        expect(Socialization.redis.smembers(backward_key(@follower1))).to match_array ["#{@followable1.class}:#{@followable1.id}"]

        @klass.follow!(@follower2, @followable1)
        expect(Socialization.redis.smembers(forward_key(@followable1))).to match_array ["#{@follower1.class}:#{@follower1.id}", "#{@follower2.class}:#{@follower2.id}"]
        expect(Socialization.redis.smembers(backward_key(@follower1))).to match_array ["#{@followable1.class}:#{@followable1.id}"]
        expect(Socialization.redis.smembers(backward_key(@follower2))).to match_array ["#{@followable1.class}:#{@followable1.id}"]
      end

      it "touches follower when instructed" do
        @klass.touch :follower
        expect(@follower1).to receive(:touch).once
        expect(@followable1).to receive(:touch).never
        @klass.follow!(@follower1, @followable1)
      end

      it "touches followable when instructed" do
        @klass.touch :followable
        expect(@follower1).to receive(:touch).never
        expect(@followable1).to receive(:touch).once
        @klass.follow!(@follower1, @followable1)
      end

      it "touches all when instructed" do
        @klass.touch :all
        expect(@follower1).to receive(:touch).once
        expect(@followable1).to receive(:touch).once
        @klass.follow!(@follower1, @followable1)
      end

      it "calls after follow hook" do
        @klass.after_follow :after_follow
        expect(@klass).to receive(:after_follow).once
        @klass.follow!(@follower1, @followable1)
      end

      it "calls after unfollow hook" do
        @klass.after_follow :after_unfollow
        expect(@klass).to receive(:after_unfollow).once
        @klass.follow!(@follower1, @followable1)
      end
    end

    describe "#unfollow!" do
      before(:each) do
        @klass.follow!(@follower1, @followable1)
      end

      it "removes follow records" do
        @klass.unfollow!(@follower1, @followable1)
        expect(Socialization.redis.smembers(forward_key(@followable1))).to be_empty
        expect(Socialization.redis.smembers(backward_key(@follower1))).to be_empty
      end
    end

    describe "#follows?" do
      it "returns true when follow exists" do
        @klass.follow!(@follower1, @followable1)
        expect(@klass.follows?(@follower1, @followable1)).to be true
      end

      it "returns false when follow doesn't exist" do
        expect(@klass.follows?(@follower1, @followable1)).to be false
      end
    end

    describe "#followers" do
      it "returns an array of followers" do
        follower1 = ImAFollower.create
        follower2 = ImAFollower.create
        follower1.follow!(@followable1)
        follower2.follow!(@followable1)
        expect(@klass.followers(@followable1, follower1.class)).to match_array [follower1, follower2]
      end

      it "returns an array of follower ids when plucking" do
        follower1 = ImAFollower.create
        follower2 = ImAFollower.create
        follower1.follow!(@followable1)
        follower2.follow!(@followable1)
        expect(@klass.followers(@followable1, follower1.class, :pluck => :id)).to match_array ["#{follower1.id}", "#{follower2.id}"]
      end
    end

    describe "#followables" do
      it "returns an array of followables" do
        followable1 = ImAFollowable.create
        followable2 = ImAFollowable.create
        @follower1.follow!(followable1)
        @follower1.follow!(followable2)

        expect(@klass.followables(@follower1, followable1.class)).to match_array [followable1, followable2]
      end

      it "returns an array of followables ids when plucking" do
        followable1 = ImAFollowable.create
        followable2 = ImAFollowable.create
        @follower1.follow!(followable1)
        @follower1.follow!(followable2)
        expect(@klass.followables(@follower1, followable1.class, :pluck => :id)).to match_array ["#{followable1.id}", "#{followable2.id}"]
      end
    end

    describe "#generate_forward_key" do
      it "returns valid key when passed an object" do
        expect(forward_key(@followable1)).to eq("Followers:#{@followable1.class.name}:#{@followable1.id}")
      end

      it "returns valid key when passed a String" do
        expect(forward_key("Followable:1")).to eq("Followers:Followable:1")
      end
    end

    describe "#generate_backward_key" do
      it "returns valid key when passed an object" do
        expect(backward_key(@follower1)).to eq("Followables:#{@follower1.class.name}:#{@follower1.id}")
      end

      it "returns valid key when passed a String" do
        expect(backward_key("Follower:1")).to eq("Followables:Follower:1")
      end
    end

    describe "#remove_followers" do
      it "deletes all followers relationships for a followable" do
        @follower1.follow!(@followable1)
        @follower2.follow!(@followable1)
        expect(@followable1.followers(@follower1.class).count).to eq(2)

        @klass.remove_followers(@followable1)
        expect(@followable1.followers(@follower1.class).count).to eq(0)
        expect(Socialization.redis.smembers(forward_key(@followable1))).to be_empty
        expect(Socialization.redis.smembers(backward_key(@follower1))).to be_empty
        expect(Socialization.redis.smembers(backward_key(@follower2))).to be_empty
      end
    end

    describe "#remove_followables" do
      it "deletes all followables relationships for a follower" do
        @follower1.follow!(@followable1)
        @follower1.follow!(@followable2)
        expect(@follower1.followables(@followable1.class).count).to eq(2)

        @klass.remove_followables(@follower1)
        expect(@follower1.followables(@followable1.class).count).to eq(0)
        expect(Socialization.redis.smembers backward_key(@followable1)).to be_empty
        expect(Socialization.redis.smembers backward_key(@follower2)).to be_empty
        expect(Socialization.redis.smembers forward_key(@follower1)).to be_empty
      end
    end

    describe "#key_type_to_type_names" do
      it "returns the proper arrays" do
        expect(@klass.send(:key_type_to_type_names, Socialization::RedisStores::Follow)).to eq(['follower', 'followable'])
        expect(@klass.send(:key_type_to_type_names, Socialization::RedisStores::Mention)).to eq(['mentioner', 'mentionable'])
        expect(@klass.send(:key_type_to_type_names, Socialization::RedisStores::Like)).to eq(['liker', 'likeable'])
      end
    end
  end

  # Helpers
  def forward_key(followable)
    Socialization::RedisStores::Follow.send(:generate_forward_key, followable)
  end

  def backward_key(follower)
    Socialization::RedisStores::Follow.send(:generate_backward_key, follower)
  end
end