require "support/crud_controller_test_helper"

RSpec.configure do |c|
  c.before failing: true do
    allow_any_instance_of(model_class).to receive(:save).and_return(false)
    allow_any_instance_of(model_class).to receive(:destroy).and_return(false)
  end
end

# A set of examples to include into the tests for your crud controller
# subclasses. Simply #let :test_entry, :new_entry_attrs and :edit_entry_attrs
# to test the basic crud functionality.
# If single examples do not match with you implementation, you may skip
# them by passing a skip parameter with context arrays:
#   include_examples 'crud controller',
#                    skip: [%w(index html sort) %w(destroy json)]
shared_examples "crud controller" do |options|
  include CrudControllerTestHelper

  render_views

  subject { response }

  let(:model_class)      { controller.send(:model_class) }
  let(:model_identifier) { controller.model_identifier }
  let(:test_params)      { scope_params }
  let(:sort_column)      { model_class.column_names.first }
  let(:ivar_name)        { controller.send(:ivar_name, model_class) }
  let(:entry)            { ivar(ivar_name) }
  let(:entries)          { ivar(ivar_name.pluralize) }
  let(:search_value) do
    field = controller.search_columns.first
    val = test_entry[field].to_s
    val[0..((val.size + 1) / 2)]
  end

  before do
    m = RSpec.current_example.metadata
    perform_combined_request if m[:perform_request] != false && m[:action] && m[:method]
  end

  describe_action :get, :index,
                  unless: skip?(options, "index") do
    context ".html",
            format: :html,
            unless: skip?(options, %w[index html]) do
      context "plain",
              unless: skip?(options, %w[index html plain]),
              combine: "ihp" do
        it_is_expected_to_respond
      end

      context "search",
              if: described_class.search_columns.present?,
              unless: skip?(options, %w[index html search]),
              combine: "ihse" do
        let(:params) { { q: search_value } }

        it_is_expected_to_respond

        context "entries" do
          subject { entries }
          it { is_expected.to include(test_entry) }
        end
      end

      context "sort",
              unless: skip?(options, %w[index html sort]) do
        context "ascending",
                unless: skip?(options, %w[index html sort ascending]),
                combine: "ihso" do
          let(:params) { { sort: sort_column, sort_dir: "asc" } }

          it_is_expected_to_respond

          it "has sorted entries" do
            sorted = entries.sort_by(&sort_column.to_sym)
            expect(entries).to eq(sorted)
          end
        end

        context "descending",
                unless: skip?(options, %w[index html sort descending]),
                combine: "ihsd" do
          let(:params) { { sort: sort_column, sort_dir: "desc" } }

          it_is_expected_to_respond

          it "has sorted entries" do
            sorted = entries.sort_by(&sort_column.to_sym)
            expect(entries).to eq(sorted.reverse)
          end
        end
      end
    end

    context ".json",
            format: :json,
            unless: skip?(options, %w[index json]),
            combine: "ij" do
      it_is_expected_to_respond
      it { expect(response.body).to start_with("[{") }
    end
  end

  describe_action :get, :show,
                  id: true,
                  unless: skip?(options, "show") do
    context ".html",
            format: :html,
            unless: skip?(options, %w[show html]) do
      context "plain",
              unless: skip?(options, %w[show html plain]),
              combine: "sh" do
        it_is_expected_to_respond
      end

      context "with non-existing id",
              unless: skip?(options,
                            %w[show html with_non_existing_id]) do
        let(:params) { { id: 9999 } }

        it "raises RecordNotFound", perform_request: false do
          expect { perform_request }
            .to raise_error(ActiveRecord::RecordNotFound)
        end
      end
    end

    context ".json",
            format: :json,
            unless: skip?(options, %w[show json]),
            combine: "sj" do
      it_is_expected_to_respond
      it_is_expected_to_render_json
    end
  end

  describe_action :get, :new,
                  unless: skip?(options, %w[new]) do
    context "plain",
            unless: skip?(options, %w[new plain]),
            combine: "new" do
      it_is_expected_to_respond
      it_is_expected_to_persist_entry(persist: false)
    end

    context "with params",
            unless: skip?(options, %w[new with_params]) do
      let(:params) { { model_identifier => new_entry_attrs } }
      it_is_expected_to_set_attrs(:new)
    end
  end

  describe_action :post, :create,
                  unless: skip?(options, %w[create]) do
    let(:params) { { model_identifier => new_entry_attrs } }

    it "adds entry to database", perform_request: false do
      expect { perform_request }.to change { model_class.count }.by(1)
    end

    context "html",
            format: :html,
            unless: skip?(options, %w[create html]) do
      it_is_expected_to_persist_entry # cannot combine this

      context "with valid params",
              unless: skip?(options, %w[create html valid]),
              combine: "chv" do
        it_is_expected_to_redirect_to_show
        it_is_expected_to_set_attrs(:new)
        it_is_expected_to_have_flash(:notice)
      end

      context "with invalid params",
              failing: true,
              unless: skip?(options, %w[create html invalid]),
              combine: "chi" do
        it_is_expected_to_persist_entry(persist: false)
        it_is_expected_to_set_attrs(:new)
        it_is_expected_to_not_have_flash(:notice)
      end
    end

    context "json",
            format: :json,
            unless: skip?(options, %w[create json]) do
      it_is_expected_to_persist_entry # cannot combine this

      context "with valid params",
              unless: skip?(options, %w[create json valid]),
              combine: "cjv" do
        it_is_expected_to_respond(201)
        it_is_expected_to_set_attrs(:new)
        it_is_expected_to_render_json
      end

      context "with invalid params",
              failing: true,
              unless: skip?(options, %w[create json invalid]),
              combine: "cji" do
        it_is_expected_to_respond(422)
        it_is_expected_to_set_attrs(:new)
        it_is_expected_to_render_json
        it_is_expected_to_persist_entry(persist: false)
      end
    end
  end

  describe_action :get, :edit,
                  id: true,
                  unless: skip?(options, %w[edit]),
                  combine: "edit" do
    it_is_expected_to_respond
  end

  describe_action :put, :update,
                  id: true,
                  unless: skip?(options, %w[update]) do
    let(:params) { { model_identifier => edit_entry_attrs } }

    it "updates entry in database", perform_request: false do
      expect { perform_request }.to change { model_class.count }.by(0)
    end

    context ".html",
            format: :html,
            unless: skip?(options, %w[update html]) do
      context "with valid params",
              unless: skip?(options, %w[update html valid]),
              combine: "uhv" do
        it_is_expected_to_set_attrs(:edit)
        it_is_expected_to_redirect_to_show
        it_is_expected_to_persist_entry
        it_is_expected_to_have_flash(:notice)
      end

      context "with invalid params",
              failing: true,
              unless: skip?(options, %w[update html invalid]),
              combine: "uhi" do
        it_is_expected_to_set_attrs(:edit)
        it_is_expected_to_not_have_flash(:notice)
      end
    end

    context ".json",
            format: :json,
            unless: skip?(options, %w[update json]) do
      context "with valid params",
              unless: skip?(options, %w[update json valid]),
              combine: "ujv" do
        it_is_expected_to_respond(200)
        it_is_expected_to_set_attrs(:edit)
        it_is_expected_to_render_json
        it_is_expected_to_persist_entry
      end

      context "with invalid params",
              failing: true,
              unless: skip?(options, %w[update json invalid]),
              combine: "uji" do
        it_is_expected_to_respond(422)
        it_is_expected_to_set_attrs(:edit)
        it_is_expected_to_render_json
      end
    end
  end

  describe_action :delete, :destroy,
                  id: true,
                  unless: skip?(options, %w[destroy]) do
    it "removes entry from database", perform_request: false do
      expect { perform_request }.to change { model_class.count }.by(-1)
    end

    context ".html",
            format: :html,
            unless: skip?(options, %w[destroy html]) do
      context "successfull", combine: "dhs" do
        it_is_expected_to_redirect_to_index
        it_is_expected_to_have_flash(:notice)
      end

      context "with failure", failing: true, combine: "dhf" do
        it_is_expected_to_redirect_to_index
        it_is_expected_to_have_flash(:alert)
      end
    end

    context ".json",
            format: :json,
            unless: skip?(options, %w[destroy json]) do
      context "successfull", combine: "djs" do
        it_is_expected_to_respond(204)
        it { expect(response.body).to be_blank }
      end

      context "with failure", failing: true, combine: "djf" do
        it_is_expected_to_respond(422)
        it_is_expected_to_render_json
      end
    end
  end
end
