require "spec_helper"

if mongo_orm == "mongo"
  describe MongoStore::Session do
    before { described_class.collection.drop }

    describe ".database" do
      subject { described_class.database }

      context "with database" do
        let(:database) do
          MongoStore::Session.database = Mongo::Client.new(
            ["127.0.0.1:27017"],
            :database => TestDatabaseHelper.test_database_name
          )
        end
        around do |example|
          original_db = described_class.database
          described_class.database = database
          example.run
          described_class.database = original_db
        end

        it "returns the database" do
          expect(subject).to eq(database)
        end
      end

      context "without database" do
        it "raises an error" do
          original_db = described_class.database
          described_class.database = nil

          expect { subject }.to raise_error(described_class::NoMongoClientError)

          described_class.database = original_db
        end
      end
    end

    describe ".collection_name" do
      # Where the name comes from:
      # - "test_database" set in spec_helper
      # - "sessions" is derived from model name
      it "saves in the given collection_name" do
        expect(described_class.collection.namespace).to eq("test_database.sessions")
      end
    end

    describe ".where" do
      subject { described_class.where(:_id => id) }

      context "without matching records" do
        let(:id) { nil }

        it "returns an empty array" do
          expect(subject).to eq([])
        end
      end

      context "with matching records" do
        let(:session) { described_class.new(:_id => generate_sid).tap(&:save) }
        let(:id) { session._id }
        before do
          # Noise
          described_class.new(:_id => generate_sid).tap(&:save)
        end

        it "returns session model objects" do
          expect(subject.map(&:_id)).to eq([session._id])
        end
      end
    end

    describe ".load" do
      let(:created_at) { Time.local(2016, 5, 27, 12, 25, 15) }
      let(:updated_at) { Time.local(2016, 5, 28, 16, 15, 55) }
      let(:session) { described_class.load(attributes) }

      context "without data" do
        let(:attributes) do
          {
            :_id => generate_sid,
            :created_at => created_at,
            :updated_at => updated_at
          }
        end

        it "creates a session object with empty data" do
          expect(session._id).to be_kind_of(String)
          expect(session.data).to eq({})
          expect(session.created_at).to eq(created_at)
          expect(session.updated_at).to eq(updated_at)
        end
      end

      context "with data key as a symbol" do
        let(:attributes) do
          {
            :_id => generate_sid,
            :data => BSON::Binary.new(Marshal.dump(:foo => "bar"), :generic),
            :created_at => created_at,
            :updated_at => updated_at
          }
        end

        it "creates a session object with correct data" do
          expect(session._id).to be_kind_of(String)
          expect(session.data).to eq(:foo => "bar")
          expect(session.created_at).to eq(created_at)
          expect(session.updated_at).to eq(updated_at)
        end
      end

      context "with data key as a string" do
        let(:attributes) do
          {
            :_id => generate_sid,
            "data" => BSON::Binary.new(Marshal.dump(:foo => "bar"), :generic),
            :created_at => created_at,
            :updated_at => updated_at
          }
        end

        it "creates a session object with correct data" do
          expect(session._id).to be_kind_of(String)
          expect(session.data).to eq(:foo => "bar")
          expect(session.created_at).to eq(created_at)
          expect(session.updated_at).to eq(updated_at)
        end
      end
    end

    describe "#save" do
      let(:id) { generate_sid }
      subject { described_class.collection.find(:_id => id).first }

      describe "_id attribute" do
        let(:session) { described_class.new(:_id => id) }
        before { session.save }

        it "saves a record in MongoDB" do
          expect(subject[:_id]).to eq(id)
        end
      end

      describe "timestamps" do
        let!(:time) { Time.now.utc }
        let(:session) { described_class.new(:_id => id) }

        describe "#created_at" do
          context "without time" do
            before { session.save }

            it "sets the current time" do
              expect(subject[:created_at]).to be_kind_of(Time)
              expect(subject[:created_at].to_s).to eq(time.to_s)
            end
          end

          context "with time" do
            let!(:original_created_at) { Time.parse("Thu Nov 29 14:33:20 2001").utc }
            before { session.created_at = original_created_at }

            it "keeps original created_at time" do
              expect do
                session.save
              end.to_not change { session.created_at.to_s }

              expect(subject[:created_at]).to be_kind_of(Time)
              expect(subject[:created_at].to_s).to eq(original_created_at.to_s)
            end
          end
        end

        describe "#updated_at" do
          context "without time" do
            it "sets the current time" do
              expect do
                session.save
              end.to change { session.updated_at.to_s }.from("").to(time.to_s)

              expect(subject[:updated_at]).to be_kind_of(Time)
              expect(subject[:updated_at].to_s).to eq(time.to_s)
            end
          end

          context "with time" do
            let!(:original_updated_at) { Time.parse("Thu Nov 29 14:33:20 2001").utc }
            before { session.updated_at = original_updated_at }

            it "updates the time to the current time" do
              expect do
                session.save
              end.to change { session.updated_at.to_s }.from(original_updated_at.to_s).to(time.to_s)

              expect(subject[:updated_at]).to be_kind_of(Time)
              expect(subject[:updated_at].to_s).to eq(time.to_s)
            end
          end
        end
      end

      context "with data attribute" do
        before do
          session = described_class.new(
            :_id => id,
            :data => { :foo => "bar" }
          )
          session.save
        end

        it "saves the marshalled data" do
          expect(subject[:data]).to be_kind_of(BSON::Binary)
          unmarshalled_data = Marshal.load(StringIO.new(subject[:data].data))
          expect(unmarshalled_data).to eq(:foo => "bar")
        end
      end

      context "without data attribute" do
        before do
          session = described_class.new(:_id => id)
          session.save
        end

        it "saves empty hash" do
          expect(subject[:data]).to be_kind_of(BSON::Binary)
          unmarshalled_data = Marshal.load(StringIO.new(subject[:data].data))
          expect(unmarshalled_data).to eq({})
        end
      end
    end

    describe "#destroy" do
      let(:id) { generate_sid }
      let(:session) { described_class.new(:_id => id) }
      before { session.save }

      it "removes the record from the collection" do
        expect do
          session.destroy
        end.to change { described_class.collection.count }.from(1).to(0)
        expect(described_class.collection.find(:_id => session._id).count).to eq(0)
      end
    end
  end
end
