require 'rails_helper'
require 'controller_actions_helper'

RSpec::Matchers.define :create_hash_for_model do |model|
  match do |model_hash|
    model_hash.is_a?(Hash) && (
      model_hash.keys.include?(model.to_s.underscore) || model_hash.keys.include?(model.to_s.camelize)
    )
  end
  description { "is a create hash for a #{model}" }
end

admin_controller_dir = Dir[Rails.root.join('app/controllers/admin/*.rb')]
admin_controller_dir.each do |filename|
  begin
    base_file = File.basename(filename, '.rb')
    controller = "Admin::#{base_file.camelize}".constantize
    # convert "a_reports_controller" to "report"
    model_name = base_file[2..base_file.length - 12].singularize
    model = model_name.camelize.constantize
  rescue NameError => ex
    # If there was a NameError, it means that either the controller or the model
    # could not be found. Don't bother with this one, move on.
    next
  rescue LoadError => ex
    # If there was a LoadError, it means that either the controller or the model
    # could not be found. Don't bother with this one, move on.
    next
  end

  describe controller do
    before(:each) do
      @obj1 = create(model_name.to_sym)
      @obj2 = create(model_name.to_sym)
      allow_any_instance_of(User).to receive(:power_user?).
        and_return(true)
      allow_any_instance_of(User).to receive(:can).
        with(anything, anything).and_return(false)
      allow_any_instance_of(User).to receive(:can).
        with(:read, anything).and_return(true)
    end

    # *** INDEX METHOD TESTS ***
    if controller.method_defined?(:index)
      context 'GET index' do
        it 'fails when user is not authenticated' do
          get :index
          expect(response.status).to eq(302)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          get :index
          expect(response.status).to eq(403)
        end

        it 'renders status 200 for power users' do
          establish_current_user(create(:user))
          get :index
          expect(response.status).to eq(200)
        end
      end
    end

    # *** SHOW METHOD TESTS ***
    if controller.method_defined?(:show)
      context 'GET show' do
        it 'fails when user is not authenticated' do
          get :show, params: { id: @obj1.uuid }
          expect(response.status).to eq(302)
        end

        it 'denies access when user lacks read access to this node' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:read, @obj1).and_return(false)
          establish_current_user(user)
          get :show, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).with(:read, @obj1).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          get :show, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'allows access when user has read access to this node' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:read, @obj1).and_return(true)
          establish_current_user(user)
          get :show, params: { id: @obj1.uuid }
          expect(response.status).to eq(200)
        end

        it 'throws an exception if node is not found' do
          establish_current_user(create(:user))
          expect do
            get :show, params: { id: 'nonexistent node' }
          end.
            to raise_error(StandardError)
        end

        it 'retrieves the requested node' do
          establish_current_user(create(:user))
          get :show, params: { id: @obj1.uuid }
          expect(assigns(:model)).to eq(@obj1)
        end
      end
    end

    # *** CREATE METHOD TESTS ***
    # FactoryBot makes controller.method_defined?(:create) return true always
    # so we have to do this trick
    if controller.method_defined?(:create) && (controller.new.method(:create).source_location[0] == filename)
      context 'GET create' do
        it 'fails when user is not authenticated' do
          get :create
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).and_return(true)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model)).and_return(false)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          establish_current_user(user)
          get :create
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).and_return(false)
          establish_current_user(create(:user))
          get :create
          expect(response.status).to eq(403)
        end

        it 'renders status 200 for power users with access' do
          allow_any_instance_of(User).to receive(:can).and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model)).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          establish_current_user(create(:user))
          get :create
          expect(response.status).to eq(200)
        end
      end
    end

    # *** EDIT METHOD TESTS ***
    if controller.method_defined?(:edit)
      context 'GET edit' do
        it 'fails when user is not authenticated' do
          get :edit, params: { id: @obj1.uuid }
          expect(response.status).to eq(302)
        end

        it 'denies access when user lacks update access to this node' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(false)
          establish_current_user(user)
          get :edit, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          get :edit, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'allows access when user has update access to this node' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          get :edit, params: { id: @obj1.uuid }
          expect(response.status).to eq(200)
        end

        it 'throws an exception if node is not found' do
          establish_current_user(create(:user))
          expect do
            get :edit, params: { id: 'nonexistent node' }
          end.
            to raise_error(StandardError)
        end

        it 'retrieves the requested node' do
          establish_current_user(create(:user))
          get :edit, params: { id: @obj1.uuid }
          expect(assigns(:model)).to eq(@obj1)
        end
      end
    end

    # *** MAKE_NEW METHOD TESTS ***
    # make_new is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('make_new')
      context 'POST make_new' do
        it 'requires authentication' do
          post :make_new, params: { props: attributes_for(model_name.to_sym) }
          expect(response.status).to eq(302)
        end

        it 'requires authorization for the node to be created' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          allow_any_instance_of(User).to receive(:can).and_return(true)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(false)
          establish_current_user(user)
          post :make_new, params: { props: attributes_for(model_name.to_sym) }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).and_return(false)
          establish_current_user(create(:user))
          post :make_new, params: { props: attributes_for(model_name.to_sym) }
          expect(response.status).to eq(403)
        end

        it 'renders status 200 when user is authorized for the node to be created' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          allow_any_instance_of(User).to receive(:can).and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(true)
          establish_current_user(user)
          post :make_new, params: { props: attributes_for(model_name.to_sym) }
          expect(response.status).to eq(200)
        end

        it "won't add the node without authentication" do
          expect do
            post :make_new, params: { props: attributes_for(model_name.to_sym) }
          end.to change(model, :count).by(0)
        end

        it "won't add the node without authorization" do
          user = create(:user)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          allow_any_instance_of(User).to receive(:can).and_return(true)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(false)
          establish_current_user(user)
          expect do
            post :make_new, params: { props: attributes_for(model_name.to_sym) }
          end.to change(model, :count).by(0)
        end

        it 'adds node to database when authenticated and authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:power_user?).and_return(true)
          allow_any_instance_of(User).to receive(:can).and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(true)
          establish_current_user(user)
          expect do
            post :make_new, params: { props: attributes_for(model_name.to_sym) }
          end.to change(model, :count).by(1)
        end

        if model.new.respond_to?(:created_by) && model.new.respond_to?(:created_at)
          it 'sets created_at and created_by' do
            model.destroy_all # so the created node is the only one
            fake_now = Time.parse('2016/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            allow_any_instance_of(User).to receive(:power_user?).and_return(true)
            allow_any_instance_of(User).to receive(:can).and_return(false)
            allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(true)
            establish_current_user(user)
            post :make_new, params: { props: attributes_for(model_name.to_sym) }
            obj = model.first
            expect(obj.created_at).to eq(fake_now)
            expect(obj.created_by).to eq('foo_user')
          end
        end

        if model.new.respond_to?(:updated_by) && model.new.respond_to?(:updated_at)
          it 'sets updated_at and updated_by' do
            model.destroy_all # so the created node is the only one
            fake_now = Time.parse('2017/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            allow_any_instance_of(User).to receive(:power_user?).and_return(true)
            allow_any_instance_of(User).to receive(:can).and_return(false)
            allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(true)
            establish_current_user(user)
            post :make_new, params: { props: attributes_for(model_name.to_sym) }
            obj = model.first
            expect(obj.updated_at).to eq(fake_now)
            expect(obj.updated_by).to eq('foo_user')
          end
        end

        if model.respond_to?(:key_prop) && model.key_prop != :uuid
          it 'returns an error when key_prop uniqueness fails' do
            user = create(:user)
            allow_any_instance_of(User).to receive(:power_user?).and_return(true)
            allow_any_instance_of(User).to receive(:can).and_return(false)
            allow_any_instance_of(User).to receive(:can).with(:create, create_hash_for_model(model_name)).and_return(true)
            establish_current_user(user)
            create_attrs = attributes_for(model_name.to_sym)
            model.create(create_attrs)
            # now this node already exists; try to do a post make_new, it should fail
            post :make_new, params: { props: create_attrs }
            expect(response.status).to eql(500)
          end
        end
      end
    end

    # *** DELETE_OLD METHOD TESTS ***
    # delete_old is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('delete_old')
      context 'DELETE delete_old' do
        it 'requires authentication' do
          delete :delete_old, params: { id: @obj1.uuid }
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(false)
          establish_current_user(user)
          delete :delete_old, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          delete :delete_old, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'renders status 200 when user is authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(true)
          establish_current_user(user)
          delete :delete_old, params: { id: @obj1.uuid }
          expect(response.status).to eq(200)
        end

        it "won't delete the node without authentication" do
          expect do
            delete :delete_old, params: { id: @obj1.uuid }
          end.to change(model, :count).by(0)
        end

        it "won't delete the node without authorization" do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(false)
          establish_current_user(user)
          expect do
            delete :delete_old, params: { id: @obj1.uuid }
          end.to change(model, :count).by(0)
        end

        it 'deletes node from database when authenticated and authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(true)
          establish_current_user(user)
          expect do
            delete :delete_old, params: { id: @obj1.uuid }
          end.to change(model, :count).by(-1)
        end

        it 'returns 500 when deletion fails' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:delete, model).and_return(true)
          allow_any_instance_of(model).to receive(:destroy).and_return(false)
          establish_current_user(user)
          delete :delete_old, params: { id: @obj1.uuid }
          expect(response.status).to eq(500)
        end
      end
    end

    # *** MODIFY METHOD TESTS ***
    # modify is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('modify')
      context 'PUT modify' do
        it 'requires authentication' do
          prop = model.new.serialize_props[0]
          expect_any_instance_of(model).to_not receive(:deserialize)
          put :modify, params: { id: @obj1.uuid,
                                 prop: prop,
                                 value: 'foo' }
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          prop = model.new.serialize_props[0]
          user = create(:user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(false)
          establish_current_user(user)
          expect_any_instance_of(model).to_not receive(:deserialize)
          put :modify, params: { id: @obj1.uuid,
                                 prop: prop,
                                 value: 'foo' }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          prop = model.new.serialize_props[0]
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          expect_any_instance_of(model).to_not receive(:deserialize)
          put :modify, params: { id: @obj1.uuid,
                                 prop: prop,
                                 value: 'foo' }
          expect(response.status).to eq(403)
        end

        it 'renders status 200 when user is authorized' do
          prop = model.new.serialize_props[0]
          user = create(:user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          put :modify, params: { id: @obj1.uuid,
                                 prop: prop,
                                 value: 'foo' }
          expect(response.status).to eq(200)
        end

        it 'returns forbidden if the prop does not exist' do
          prop = model.new.serialize_props[0]
          user = create(:user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          expect_any_instance_of(model).to_not receive(:deserialize)
          put :modify, params: { id: @obj1.uuid,
                                 prop: 'this is not a prop you should ever have',
                                 value: 'foo' }
          expect(response.status).to eq(403)
        end

        # We don't actually test that the change gets made; I can think of no
        # good way to do a "generic" test of this. We just check that it gets
        # sent to deserialize with all correct data. Let serializable_node_spec
        # take it from there.
        it 'sends a property change to deserialize, with updated_at and updated_by' do
          prop = model.new.serialize_props[0]
          fake_now = Time.parse('2018/01/01 12:00')
          allow(Time).to receive(:now).and_return(fake_now)
          user = User.create(net_id: 'foo_user')
          establish_current_user(user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(true)
          expect_any_instance_of(model).
            to receive(:deserialize).with({ prop => 'foo',
                                            'updated_at' => fake_now,
                                            'updated_by' => 'foo_user' }, 'foo_user')
          put :modify, params: { id: @obj1.uuid,
                                 prop: prop,
                                 value: 'foo' }
        end

        if model.new.serialize_assocs.length.positive?
          it 'sends a relationship change to deserialize, with updated_at and updated_by' do
            assoc = model.new.serialize_assocs[0].name
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            expect_any_instance_of(model).
              to receive(:deserialize).with({ assoc.to_s => { 'foo' => {}},
                                              'updated_at' => fake_now,
                                              'updated_by' => 'foo_user' }, 'foo_user')
            put :modify, params: { id: @obj1.uuid,
                                   prop: assoc,
                                   values: ['foo'] }
          end
        end

        if model.new.respond_to?(:published_at) && model.new.respond_to?(:status)
          it 'updates published_at and published_by when status becomes Approved' do
            @obj1.status = 'In Progress'
            @obj1.save
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            put :modify, params: { id: @obj1.uuid,
                                   prop: 'status',
                                   value: 'Approved' }
            @obj1.reload
            expect(@obj1.published_at).to eq(DateTime.parse(fake_now.to_s))
          end

          it 'does not update published_at when non-Approved status has not changed' do
            @obj1.status = 'In Progress'
            @obj1.save
            prop = (model.new.serialize_props - %w[status])[0]
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            expect_any_instance_of(model).
              to_not receive(:deserialize).with({ 'published_at' => fake_now,
                                                  'published_by' => 'foo_user' }, 'foo_user')
            @obj1.reload
            expect(@obj1.published_at).to eq(nil)
          end

          it 'does not update published_at when Approved status has not changed' do
            fake_then = Time.parse('2017/01/01 12:00')
            @obj1.status = 'Approved'
            @obj1.published_at = fake_then
            @obj1.save
            prop = (model.new.serialize_props - %w[status])[0]
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            expect_any_instance_of(model).
              to_not receive(:deserialize).with({ 'published_at' => fake_now,
                                                  'published_by' => 'foo_user' }, 'foo_user')
            @obj1.reload
            expect(@obj1.published_at).to eq(DateTime.parse(fake_then.to_s))
          end
        end
      end
    end

    # *** DESERIALIZE METHOD TESTS ***
    # deserialize is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('deserialize')
      context 'POST deserialize' do
        before(:each) do
          @serialized = @obj2.serialize
          @obj2.destroy
          # file properties will make a giant mess. do not send.
          if model.respond_to?(:file_props)
            model.file_props.each do |key|
              @serialized.delete(key.to_s)
              @serialized.delete("#{key}_file_name")
              @serialized.delete("#{key}_content_type")
              @serialized.delete("#{key}_file_size")
              @serialized.delete("#{key}_updated_at")
            end
          end
        end

        it 'requires authentication' do
          expect_any_instance_of(model).to_not receive(:deserialize)
          post :deserialize, params: { id: @obj1.uuid,
                                       data: @serialized }
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          user = create(:user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(false)
          establish_current_user(user)
          expect_any_instance_of(model).to_not receive(:deserialize)
          post :deserialize, params: { id: @obj1.uuid,
                                       data: @serialized }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).and_return(false)
          establish_current_user(create(:user))
          expect_any_instance_of(model).to_not receive(:deserialize)
          post :deserialize, params: { id: @obj1.uuid,
                                       data: @serialized }
          expect(response.status).to eq(403)
        end

        if model.respond_to?(:asset_owner)
          it 'denies access when user lacks create access to the specified repo' do
            allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
            allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(false)
            @serialized['asset_owner'] = { create(:departmental_repo).name => {}}
            establish_current_user(create(:user))
            expect_any_instance_of(model).to_not receive(:deserialize)
            post :deserialize, params: { id: @obj1.uuid,
                                         data: @serialized }, as: :json
            expect(response.status).to eq(403)
          end

          it 'denies access when user lacks create access for non-departmental assets and asset_owner is specified as empty' do
            allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
            allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(false)
            @serialized['asset_owner'] = {}
            establish_current_user(create(:user))
            expect_any_instance_of(model).to_not receive(:deserialize)
            post :deserialize, params: { id: @obj1.uuid,
                                         data: @serialized }, as: :json
            expect(response.status).to eq(403)
          end

          it 'allows access when user has create access to the specified repo' do
            allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
            allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
            @serialized['asset_owner'] = { create(:departmental_repo).name => {}}
            establish_current_user(create(:user))
            allow_any_instance_of(model).to receive(:deserialize).and_return(true)
            expect_any_instance_of(model).to receive(:deserialize)
            post :deserialize, params: { id: @obj1.uuid,
                                         data: @serialized }, as: :json
            expect(response.status).to eq(200)
          end

        end

        it 'renders status 200 when user is authorized' do
          user = create(:user)
          allow_any_instance_of(User).
            to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          post :deserialize, params: { id: @obj1.uuid,
                                       data: @serialized }
          expect(response.status).to eq(200)
        end

        # We don't actually test that the change gets made; I can think of no
        # good way to do a "generic" test of this. We just check that it gets
        # sent to deserialize with all correct data. Let serializable_node_spec
        # take it from there.
        it 'sends a hash to deserialize, with updated_at and updated_by' do
          fake_now = Time.parse('2018/01/01 12:00')
          allow(Time).to receive(:now).and_return(fake_now)
          user = User.create(net_id: 'foo_user')
          establish_current_user(user)
          allow_any_instance_of(User).
            to receive(:can).and_return(true)

          hash_to_expect = JSON.parse(@serialized.to_json)
          hash_to_expect['updated_at'] = fake_now
          hash_to_expect['updated_by'] = 'foo_user'

          expect_any_instance_of(model).
            to receive(:deserialize).with(hash_to_expect, 'foo_user')
          post :deserialize, params: { id: @obj1.uuid,
                                       data: @serialized }, as: :json
        end

        if model.new.respond_to?(:published_at) && model.new.respond_to?(:status)
          it 'updates published_at and published_by when status becomes Approved' do
            @obj1.status = 'In Progress'
            @obj1.save
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            hash_to_send = @serialized
            hash_to_send['status'] = 'Approved'
            @serialized.delete('published_at')
            post :deserialize, params: { id: @obj1.uuid,
                                         data: hash_to_send }
            @obj1.reload
            expect(@obj1.published_at).to eq(DateTime.parse(fake_now.to_s))
          end

          it 'does not update published_at when non-Approved status has not changed' do
            @obj1.status = 'In Progress'
            @obj1.save
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            @serialized['status'] = 'In Progress'
            @serialized.delete('published_at')
            post :deserialize, params: { id: @obj1.uuid,
                                         data: @serialized }
            @obj1.reload
            expect(@obj1.published_at).to eq(nil)
          end

          it 'does not update published_at when Approved status has not changed' do
            fake_then = Time.parse('2017/01/01 12:00')
            @obj1.status = 'Approved'
            @obj1.published_at = fake_then
            @obj1.save
            fake_now = Time.parse('2018/01/01 12:00')
            allow(Time).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            establish_current_user(user)
            allow_any_instance_of(User).
              to receive(:can).with(:update, @obj1).and_return(true)
            @serialized['status'] = 'Approved'
            @serialized.delete('published_at')
            post :deserialize, params: { id: @obj1.uuid,
                                         data: @serialized }
            @obj1.reload
            expect(@obj1.published_at).to eq(DateTime.parse(fake_then.to_s))
          end
        end
      end
    end

    # *** CLONE_EXISTING METHOD TESTS ***
    # clone_existing is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('clone_existing')
      context 'POST clone_existing' do
        it 'requires authentication' do
          post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
          allow_any_instance_of(User).to receive(:can).with(:create, { model_name.underscore => @obj1.serialize }).and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:create, { model_name.camelize => @obj1.serialize }).and_return(false)
          establish_current_user(user)
          post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          establish_current_user(create(:user))
          post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          expect(response.status).to eq(403)
        end

        it 'renders status 200 when user is authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:create, { model_name.underscore => @obj1.serialize }).and_return(true)
          allow_any_instance_of(User).to receive(:can).with(:create, { model_name.camelize => @obj1.serialize }).and_return(true)
          establish_current_user(user)
          post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          expect(response.status).to eq(200)
        end

        it "won't add the node without authentication" do
          expect do
            post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          end.to change(model, :count).by(0)
        end

        it "won't add the node without authorization" do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(false)
          establish_current_user(user)
          expect do
            post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          end.to change(model, :count).by(0)
        end

        it 'adds node to database when authenticated and authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
          establish_current_user(user)
          expect do
            post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          end.to change(model, :count).by(1)
        end

        if model.new.respond_to?(:created_by) && model.new.respond_to?(:created_at)
          it 'sets created_at and created_by' do
            fake_now = DateTime.parse('2016/01/01 12:00')
            allow(DateTime).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
            establish_current_user(user)
            post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
            obj = model.find_by(model.key_prop => 'foo clone')
            expect(obj.created_at).to eq(fake_now)
            expect(obj.created_by).to eq('foo_user')
          end
        end

        if model.new.respond_to?(:updated_by) && model.new.respond_to?(:updated_at)
          it 'sets updated_at and updated_by' do
            fake_now = DateTime.parse('2017/01/01 12:00')
            allow(DateTime).to receive(:now).and_return(fake_now)
            user = User.create(net_id: 'foo_user')
            allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
            establish_current_user(user)
            post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
            obj = model.find_by(model.key_prop => 'foo clone')
            expect(obj.updated_at).to eq(fake_now)
            expect(obj.updated_by).to eq('foo_user')
          end
        end

        it 'returns an error when key_prop uniqueness fails' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
          establish_current_user(user)
          post :clone_existing, params: { id: @obj1.uuid, key_prop: @obj2[model.key_prop] }
          expect(response.status).to eql(500)
        end

        it 'returns an error when clone_node method returns nil' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:create, anything).and_return(true)
          establish_current_user(user)
          allow_any_instance_of(model).to receive(:clone_node).and_return(nil)
          post :clone_existing, params: { id: @obj1.uuid, key_prop: 'foo clone' }
          expect(response.status).to eql(500)
        end
      end
    end

    # *** ATTACH_TO METHOD TESTS ***
    # attach_to is defined in AdminBaseController, so all controllers will
    # respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('attach_to')
      context 'POST attach_to' do
        attachable_props = model.instance_methods.select { |meth| model.accepts_file_for?(meth) }
        attachable_props.each do |attach_prop|
          before(:each) do
            # do not actually upload anything to S3
            allow_any_instance_of(model).to receive("#{attach_prop}=".to_sym)
            # This is some real hackery right here, but since Paperclip is deprecated
            # and we'll be removing it, I'm not inclined to go to great lengths to mock
            # it out properly.
            @fake_attach = double
            allow(@fake_attach).to receive(:flush_errors)
            allow(@fake_attach).to receive(:dirty?).and_return(false)
            allow(@fake_attach).to receive(:save)
            allow(@fake_attach).to receive(:exists?).and_return(true)
            allow_any_instance_of(model).to receive(attach_prop.to_sym).and_return(@fake_attach)
          end

          it "requires authentication for #{attach_prop}" do
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop,
                                       file: file_fixture('test_image.jpg') }
            expect(response.status).to eq(302)
          end

          it "requires authorization for #{attach_prop}" do
            user = create(:user)
            allow_any_instance_of(User).to receive(:can).with(:update, model).and_return(false)
            establish_current_user(user)
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop,
                                       file: file_fixture('test_image.jpg') }
            expect(response.status).to eq(403)
          end

          it "denies access for #{attach_prop} when user is not a power user" do
            allow_any_instance_of(User).to receive(:can).with(:update, model).and_return(true)
            allow_any_instance_of(User).to receive(:power_user?).
              and_return(false)
            establish_current_user(create(:user))
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop,
                                       file: file_fixture('test_image.jpg') }
            expect(response.status).to eq(403)
          end

          it "renders status 200 for #{attach_prop} when user is authorized" do
            user = create(:user)
            allow_any_instance_of(User).to receive(:can).with(:update, model).and_return(true)
            establish_current_user(user)
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop,
                                       file: file_fixture('test_image.jpg') }
            expect(response.status).to eq(200)
          end

          it "renders status 400 for #{attach_prop} if file is missing" do
            user = create(:user)
            allow_any_instance_of(User).to receive(:can).with(:update, model).and_return(true)
            establish_current_user(user)
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop }
            expect(response.status).to eq(400)
          end

          it "attaches the file for #{attach_prop}" do
            user = create(:user)
            allow_any_instance_of(User).to receive(:can).with(:update, model).and_return(true)
            establish_current_user(user)
            expect_any_instance_of(model).to receive("#{attach_prop}=".to_sym)
            post :attach_to, params: { id: @obj1.uuid,
                                       prop: attach_prop,
                                       file: file_fixture('test_image.jpg') }
          end
        end
      end
    end

    # *** UPDATE_PERMALINK METHOD TESTS ***
    # update_permalink is defined in AdminBaseController, so all controllers
    # will respond_to it. Instead we check to see if a route exists.
    if actions_for_controller(controller).include?('update_permalink')
      context 'PUT update_permalink' do
        it 'requires authentication' do
          put :update_permalink, params: { id: @obj1.uuid }
          expect(response.status).to eq(302)
        end

        it 'requires authorization' do
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(false)
          user = create(:user)
          establish_current_user(user)
          put :update_permalink, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'denies access when user is not a power user' do
          allow_any_instance_of(User).to receive(:power_user?).
            and_return(false)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          user = create(:user)
          establish_current_user(user)
          put :update_permalink, params: { id: @obj1.uuid }
          expect(response.status).to eq(403)
        end

        it 'renders status 200 when user is authorized' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          put :update_permalink, params: { id: @obj1.uuid }
          expect(response.status).to eq(200)
        end

        it 'calls update_permalink on a node' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)
          expect_any_instance_of(model).to receive(:update_permalink)
          put :update_permalink, params: { id: @obj1.uuid }
        end

        it 'returns the value of permalink after update' do
          user = create(:user)
          allow_any_instance_of(User).to receive(:can).with(:update, @obj1).and_return(true)
          establish_current_user(user)

          # set the permalink
          @obj1.permalink
          # change the name
          @obj1[model.key_prop] = 'foo bar'
          @obj1.save

          # call the update method
          put :update_permalink, params: { id: @obj1.uuid }
          res = response.body

          # now, make sure the update was performed
          @obj1.update_permalink

          # verify that the response equals the updated permalink
          expect(res).to eq(@obj1.permalink)
        end
      end
    end
  end
end
