# frozen_string_literal: true
# rubocop:todo all

require "spec_helper"

describe Mongoid::Attributes::Readonly do

  before do
    Person.attr_readonly(*attributes)
  end

  after do
    Person.readonly_attributes.reject! do |a|
      [ attributes ].flatten.include?(a.to_sym) ||
        [ attributes ].flatten.include?(Person.aliased_fields.key(a).to_sym)
    end
  end

  describe ".attr_readonly" do

    context "when providing a single field" do

      let(:attributes) do
        :title
      end

      it "adds the field to readonly attributes" do
        expect(Person.readonly_attributes.to_a).to include("title")
      end
    end

    context "when providing a field alias" do

      let(:attributes) do
        :aliased_timestamp
      end

      it "adds the database field name to readonly attributes" do
        expect(Person.readonly_attributes.to_a).to include("at")
      end
    end

    context "when providing multiple fields" do

      let(:attributes) do
        [ :title, :terms ]
      end

      it "adds the fields to readonly attributes" do
        expect(Person.readonly_attributes.to_a).to include("title", "terms")
      end
    end

    context "when creating a new document with a readonly field" do

      let(:attributes) do
        [ :title, :terms, :aliased_timestamp ]
      end

      let(:person) do
        Person.create!(title: "sir", terms: true, aliased_timestamp: Time.at(42))
      end

      it "sets the first readonly value" do
        expect(person.title).to eq("sir")
      end

      it "sets the second readonly value" do
        expect(person.terms).to be true
      end

      it "sets the third readonly value" do
        expect(person.aliased_timestamp).to eq(Time.at(42))
      end

      it "persists the first readonly value" do
        expect(person.reload.title).to eq("sir")
      end

      it "persists the second readonly value" do
        expect(person.reload.terms).to be true
      end

      it "persists the third readonly value" do
        expect(person.reload.aliased_timestamp).to eq(Time.at(42))
      end
    end

    context "when updating an existing readonly field" do

      let(:attributes) do
        [ :title, :terms, :score, :aliased_timestamp ]
      end

      let(:person) do
        Person.create!(title: "sir", terms: true, score: 1, aliased_timestamp: Time.at(42))
      end

      context "when updating via the setter" do

        it "does not update the first field" do
          person.title = 'mr'
          person.save!
          expect(person.reload.title).to eq("sir")
        end

        it "does not update the second field" do
          person.aliased_timestamp = Time.at(43)
          person.save!
          expect(person.reload.aliased_timestamp).to eq(Time.at(42))
        end
      end

      context "when updating via inc" do

        context 'with single field operation' do

          it "updates the field" do
            person.inc(score: 1)
            person.save!
            expect(person.reload.score).to eq(2)
          end
        end

        context 'with multiple fields operation' do

          it "updates the fields" do
            person.inc(score: 1, age: 1)
            person.save!
            expect(person.reload.score).to eq(2)
            expect(person.reload.age).to eq(101)
          end
        end
      end

      context "when updating via bit" do

        context 'with single field operation' do

          it "does the update" do
            person.bit(score: { or: 13 })
            person.save!
            expect(person.reload.score).to eq(13)
          end
        end

        context 'with multiple fields operation' do

          it "updates the attribute" do
            person.bit(age: {and: 13}, score: {or: 13})
            person.save!
            expect(person.reload.score).to eq(13)
            expect(person.reload.age).to eq(4)
          end
        end
      end

      context "when updating via []=" do

        it "does not update the first field" do
          person[:title] = "mr"
          person.save!
          expect(person.reload.title).to eq("sir")
        end

        it "does not update the second field" do
          person[:aliased_timestamp] = Time.at(43)
          person.save!
          expect(person.reload.aliased_timestamp).to eq(Time.at(42))
        end
      end

      context "when updating via write_attribute" do

        it "does not update the first field" do
          person.write_attribute(:title, "mr")
          person.save!
          expect(person.reload.title).to eq("sir")
        end

        it "does not update the second field" do
          person.write_attribute(:aliased_timestamp, Time.at(43))
          person.save!
          expect(person.reload.aliased_timestamp).to eq(Time.at(42))
        end
      end

      context "when updating via update_attributes" do

        it "does not update the first field" do
          person.update_attributes!(title: "mr", aliased_timestamp: Time.at(43))
          person.save!
          expect(person.reload.title).to eq("sir")
        end

        it "does not update the second field" do
          person.update_attributes!(title: "mr", aliased_timestamp: Time.at(43))
          person.save!
          expect(person.reload.aliased_timestamp).to eq(Time.at(42))
        end
      end

      context "when updating via update_attributes!" do

        it "does not update the first field" do
          person.update_attributes!(title: "mr", aliased_timestamp: Time.at(43))
          person.save!
          expect(person.reload.title).to eq("sir")
        end

        it "does not update the second field" do
          person.update_attributes!(title: "mr", aliased_timestamp: Time.at(43))
          person.save!
          expect(person.reload.aliased_timestamp).to eq(Time.at(42))
        end
      end

      context "when updating via update_attribute" do

        it "raises an error" do
          expect {
            person.update_attribute(:title, "mr")
          }.to raise_exception(Mongoid::Errors::ReadonlyAttribute)
        end
      end

      context "when updating via remove_attribute" do

        it "raises an error" do
          expect {
            person.remove_attribute(:title)
          }.to raise_exception(Mongoid::Errors::ReadonlyAttribute)
        end
      end
    end

    context 'when a foreign_key is defined as readonly' do

      let(:attributes) do
        :mother_id
      end

      context 'when the relation exists' do

        let(:mother) do
          Person.create!
        end

        let(:child) do
          Person.create!(mother: mother)
          Person.find_by(mother: mother)
        end

        it 'the relation can still be accessed' do
          expect(child.mother).to eq(mother)
        end
      end

      context 'when the relation does not exist' do

        let(:child) do
          Person.create!
        end

        it 'the relation is nil' do
          expect(child.mother).to be_nil
        end
      end
    end

    context "when a subclass inherits readonly fields" do
      let(:attributes) do
        [:title, :terms]
      end

      before do
        class OldPerson < Person
          attr_readonly :age
        end
      end

      it "ensures subclass inherits the readonly attributes from parent" do
        expect(OldPerson.readonly_attributes.to_a).to include("title","terms")
      end

      it "ensures subclass does not modify parent's readonly attributes" do
        expect(Person.readonly_attributes.to_a).not_to include("age")
      end
    end
  end
end
