consul/consul

View on GitHub
spec/models/budget/investment_spec.rb

Summary

Maintainability
F
4 days
Test Coverage
require "rails_helper"

describe Budget::Investment do
  let(:investment) { build(:budget_investment) }

  describe "Concerns" do
    it_behaves_like "notifiable"
    it_behaves_like "sanitizable"
    it_behaves_like "globalizable", :budget_investment
    it_behaves_like "acts as imageable", :budget_investment_image
    it_behaves_like "acts as paranoid", :budget_investment
  end

  it "is valid" do
    expect(investment).to be_valid
  end

  it "is not valid without an author" do
    investment.author = nil
    expect(investment).not_to be_valid
  end

  describe "#title" do
    it "is not valid without a title" do
      investment.title = nil
      expect(investment).not_to be_valid
    end

    it "is not valid when very short" do
      investment.title = "abc"
      expect(investment).not_to be_valid
    end

    it "is not valid when very long" do
      investment.title = "a" * 81
      expect(investment).not_to be_valid
    end
  end

  it "set correct group and budget ids" do
    budget = create(:budget)
    group_1 = create(:budget_group, budget: budget)
    group_2 = create(:budget_group, budget: budget)

    heading_1 = create(:budget_heading, group: group_1)
    heading_2 = create(:budget_heading, group: group_2)

    investment = create(:budget_investment, heading: heading_1)

    expect(investment.budget_id).to eq budget.id
    expect(investment.group_id).to eq group_1.id

    investment.update!(heading: heading_2)

    expect(investment.budget_id).to eq budget.id
    expect(investment.group_id).to eq group_2.id
  end

  it "logs previous heading value if it is changed" do
    budget = create(:budget, phase: "balloting")

    group = create(:budget_group, budget: budget)

    heading_1 = create(:budget_heading, group: group)
    heading_2 = create(:budget_heading, group: group)

    investment = create(:budget_investment, heading: heading_1)

    expect(investment.previous_heading_id).to be nil

    investment.update!(heading: heading_2)

    expect(investment.previous_heading_id).to eq heading_1.id
  end

  it "stores original heading id" do
    investment = create(:budget_investment)

    expect(investment.original_heading_id).to eq investment.heading_id
  end

  describe "#unfeasibility_explanation blank" do
    it "is valid if valuation not finished" do
      investment.unfeasibility_explanation = ""
      investment.valuation_finished = false
      expect(investment).to be_valid
    end

    it "is valid if valuation finished and feasible" do
      investment.unfeasibility_explanation = ""
      investment.feasibility = "feasible"
      investment.valuation_finished = true
      expect(investment).to be_valid
    end

    it "is not valid if valuation finished and unfeasible" do
      investment.unfeasibility_explanation = ""
      investment.feasibility = "unfeasible"
      investment.valuation_finished = true
      expect(investment).not_to be_valid
    end
  end

  describe "#price blank" do
    it "is valid if valuation not finished" do
      investment.price = ""
      investment.valuation_finished = false
      expect(investment).to be_valid
    end

    it "is valid if valuation finished and unfeasible" do
      investment.price = ""
      investment.unfeasibility_explanation = "reason"
      investment.feasibility = "unfeasible"
      investment.valuation_finished = true
      expect(investment).to be_valid
    end

    it "is not valid if valuation finished and feasible" do
      investment.price = ""
      investment.feasibility = "feasible"
      investment.valuation_finished = true
      expect(investment).not_to be_valid
    end
  end

  describe "#code" do
    let(:investment) { create(:budget_investment) }

    it "returns the proposal id" do
      expect(investment.code).to include(investment.id.to_s)
    end

    it "returns the administrator id when assigned" do
      investment.administrator = create(:administrator)
      expect(investment.code).to include("#{investment.id}-A#{investment.administrator.id}")
    end
  end

  describe "#send_unfeasible_email" do
    let(:investment) { create(:budget_investment) }

    it "sets the time when the unfeasible email was sent" do
      expect(investment.unfeasible_email_sent_at).not_to be
      investment.send_unfeasible_email
      expect(investment.unfeasible_email_sent_at).to be
    end

    it "send an email" do
      expect { investment.send_unfeasible_email }.to change { ActionMailer::Base.deliveries.count }.by(1)
    end
  end

  describe "#should_show_votes?" do
    it "returns true in selecting phase" do
      budget = create(:budget, :selecting)
      investment = create(:budget_investment, budget: budget)

      expect(investment.should_show_votes?).to be true
    end

    it "returns false in any other phase" do
      Budget::Phase::PHASE_KINDS.excluding("selecting").each do |phase|
        budget = create(:budget, phase: phase)
        investment = create(:budget_investment, budget: budget)

        expect(investment.should_show_votes?).to be false
      end
    end
  end

  describe "#should_show_vote_count?" do
    it "returns true in valuating phase" do
      budget = create(:budget, :valuating)
      investment = create(:budget_investment, budget: budget)

      expect(investment.should_show_vote_count?).to be true
    end

    it "returns false in any other phase" do
      Budget::Phase::PHASE_KINDS.excluding("valuating").each do |phase|
        budget = create(:budget, phase: phase)
        investment = create(:budget_investment, budget: budget)

        expect(investment.should_show_vote_count?).to be false
      end
    end
  end

  describe "#should_show_ballots?" do
    it "returns true in balloting phase for selected investments" do
      budget = create(:budget, :balloting)
      investment = create(:budget_investment, :selected, budget: budget)

      expect(investment.should_show_ballots?).to be true
    end

    it "returns false for unselected investments" do
      budget = create(:budget, :balloting)
      investment = create(:budget_investment, :unselected, budget: budget)

      expect(investment.should_show_ballots?).to be false
    end

    it "returns false in any other phase" do
      Budget::Phase::PHASE_KINDS.excluding("balloting").each do |phase|
        budget = create(:budget, phase: phase)
        investment = create(:budget_investment, :selected, budget: budget)

        expect(investment.should_show_ballots?).to be false
      end
    end
  end

  describe "#should_show_price?" do
    let(:budget) { create(:budget, :publishing_prices) }
    let(:investment) do
      create(:budget_investment, :selected, budget: budget)
    end

    it "returns true for selected investments which budget's phase is publishing_prices or later" do
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_price?).to be true
      end
    end

    it "returns false in any other phase" do
      (Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_price?).to be false
      end
    end

    it "returns false if investment is not selected" do
      investment.selected = false

      expect(investment.should_show_price?).to be false
    end

    it "returns false if price is not present" do
      investment.price = nil

      expect(investment.should_show_price?).to be false
    end

    it "returns false if budget hide money is active" do
      budget.update!(hide_money: true)

      expect(investment.should_show_price?).to be false
    end
  end

  describe "#should_show_price_explanation?" do
    let(:budget) { create(:budget, :publishing_prices) }
    let(:investment) do
      create(:budget_investment, :selected, budget: budget, price_explanation: "because of reasons")
    end

    it "returns true for selected with price_explanation & budget in publishing_prices or later" do
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_price_explanation?).to be true
      end
    end

    it "returns false in any other phase" do
      (Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_price_explanation?).to be false
      end
    end

    it "returns false if investment is not selected" do
      investment.selected = false

      expect(investment.should_show_price_explanation?).to be false
    end

    it "returns false if price_explanation is not present" do
      investment.price_explanation = ""

      expect(investment.should_show_price_explanation?).to be false
    end
  end

  describe "#should_show_unfeasibility_explanation?" do
    let(:budget) { create(:budget) }
    let(:investment) do
      create(:budget_investment, :unfeasible, :finished, budget: budget)
    end

    it "returns true for unfeasible investments with unfeasibility explanation and valuation finished" do
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_unfeasibility_explanation?).to be true
      end
    end

    it "returns false in valuation has not finished" do
      investment.update!(valuation_finished: false)
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_unfeasibility_explanation?).to be false
      end
    end

    it "returns false if not unfeasible" do
      investment.update!(feasibility: "undecided")
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_unfeasibility_explanation?).to be false
      end
    end

    it "returns false if unfeasibility explanation blank" do
      investment.unfeasibility_explanation = ""
      Budget::Phase::PUBLISHED_PRICES_PHASES.each do |phase|
        budget.update!(phase: phase)

        expect(investment.should_show_unfeasibility_explanation?).to be false
      end
    end
  end

  describe "#by_budget" do
    it "returns investments scoped by budget" do
      budget1 = create(:budget)
      budget2 = create(:budget)

      group1 = create(:budget_group, budget: budget1)
      group2 = create(:budget_group, budget: budget2)

      heading1 = create(:budget_heading, group: group1)
      heading2 = create(:budget_heading, group: group2)

      investment1 = create(:budget_investment, heading: heading1)
      investment2 = create(:budget_investment, heading: heading1)
      investment3 = create(:budget_investment, heading: heading2)

      investments_by_budget = Budget::Investment.by_budget(budget1)

      expect(investments_by_budget).to match_array [investment1, investment2]
      expect(investments_by_budget).not_to include investment3
    end
  end

  describe "#by_admin" do
    it "returns investments assigned to specific administrator" do
      investment1 = create(:budget_investment, administrator_id: 33)
      create(:budget_investment)

      by_admin = Budget::Investment.by_admin(33)

      expect(by_admin).to eq [investment1]
    end
  end

  describe "by_valuator" do
    it "returns investments assigned to a valuator" do
      alfred = create(:valuator)
      batman = create(:valuator)

      manor = create(:budget_investment, valuators: [alfred])
      batcave = create(:budget_investment, valuators: [alfred, batman])

      by_valuator = Budget::Investment.by_valuator(alfred)

      expect(by_valuator).to match_array [manor, batcave]
    end

    it "does not return investments assigned to a different valuator" do
      jekyll = create(:valuator)
      hyde = create(:valuator)

      create(:budget_investment, valuators: [hyde])

      by_valuator = Budget::Investment.by_valuator(jekyll)

      expect(by_valuator).to be_empty
    end
  end

  describe "#by_valuator_group" do
    let(:aquaman)        { create(:valuator) }
    let(:justice_league) { create(:valuator_group, valuators: [aquaman]) }

    it "returns investments assigned to a valuator's group" do
      water_power = create(:budget_investment, valuator_groups: [justice_league], valuators: [aquaman])
      solar_power = create(:budget_investment, valuator_groups: [justice_league])

      by_valuator_group = Budget::Investment.by_valuator_group(justice_league)

      expect(by_valuator_group).to contain_exactly(solar_power, water_power)
    end

    it "does not return investments assigned to no groups" do
      create(:budget_investment, valuators: [aquaman], valuator_groups: [])

      expect(Budget::Investment.by_valuator_group(justice_league)).to be_empty
    end

    it "does not return investments assigned to a different group" do
      create(:budget_investment, valuators: [aquaman], valuator_groups: [create(:valuator_group)])

      expect(Budget::Investment.by_valuator_group(justice_league)).to be_empty
    end
  end

  describe ".visible_to_valuator" do
    let(:valuator) { create(:valuator) }

    it "returns investments assigned to the valuator" do
      investment = create(:budget_investment, :visible_to_valuators, valuators: [valuator])

      expect(Budget::Investment.visible_to_valuator(valuator)).to eq [investment]
    end

    it "does not return investments assigned to other valuators" do
      create(:budget_investment, :visible_to_valuators, valuators: [create(:valuator)])

      expect(Budget::Investment.visible_to_valuator(valuator)).to be_empty
    end

    it "does not return duplicate investments when they're assigned more than once" do
      investment = create(:budget_investment, :visible_to_valuators)
      2.times { Budget::ValuatorAssignment.create!(valuator: valuator, investment: investment) }

      expect(Budget::Investment.visible_to_valuator(valuator)).to eq [investment]
    end

    it "does not return duplicate investments when assigned to both a valuator and their group" do
      valuator_group = create(:valuator_group, valuators: [valuator])
      investment = create(:budget_investment, :visible_to_valuators, valuators: [valuator],
                                                                     valuator_groups: [valuator_group])

      expect(Budget::Investment.visible_to_valuator(valuator)).to eq [investment]
    end

    it "returns investments assigned to the valuator's group" do
      valuator_group = create(:valuator_group, valuators: [valuator])
      investment = create(:budget_investment, :visible_to_valuators, valuator_groups: [valuator_group])

      expect(Budget::Investment.visible_to_valuator(valuator)).to eq [investment]
    end

    it "does not return investments assigned to other valuator groups" do
      valuator_group = create(:valuator_group, valuators: [create(:valuator)])
      create(:budget_investment, :visible_to_valuators, valuator_groups: [valuator_group])

      expect(Budget::Investment.visible_to_valuator(valuator)).to be_empty
    end

    it "returns an empty relation when valuator is nil" do
      expect(Budget::Investment.visible_to_valuator(nil)).to be_empty
    end
  end

  describe ".scoped_filter" do
    let(:budget)     { create(:budget, :balloting, slug: "budget_slug") }
    let(:investment) { create(:budget_investment, budget: budget) }

    it "finds budget by id or slug" do
      results = Budget::Investment.scoped_filter({ budget_id: budget.id }, nil)

      expect(results).to eq [investment]

      results = Budget::Investment.scoped_filter({ budget_id: "budget_slug" }, nil)

      expect(results).to eq [investment]
    end

    it "does not raise error if budget is not found" do
      result = Budget::Investment.scoped_filter({ budget_id: "wrong_budget" }, nil)
      expect(result).to be_empty
    end

    describe "with without_admin filter" do
      let(:params) { { advanced_filters: ["without_admin"], budget_id: budget.id } }
      it "returns only investment without admin" do
        create(:budget_investment, :with_administrator, budget: budget)
        investment2 = create(:budget_investment, budget: budget)

        expect(Budget::Investment.scoped_filter(params, "all")).to eq([investment2])
      end
    end

    describe "with without_valuator filter" do
      let(:params) { { advanced_filters: ["without_valuator"], budget_id: budget.id } }
      it "returns only investment without valuator" do
        create(:budget_investment, :with_valuator, budget: budget)
        investment2 = create(:budget_investment, :with_administrator, budget: budget)
        investment3 = create(:budget_investment, budget: budget)

        expect(Budget::Investment.scoped_filter(params, "all"))
          .to contain_exactly(investment2, investment3)
      end
    end

    describe "with under_valuation filter" do
      let(:params) { { advanced_filters: ["under_valuation"], budget_id: budget.id } }
      it "returns only investment under valuation" do
        investment1 = create(:budget_investment, :with_administrator, :open, :with_valuator,
                             budget: budget)
        create(:budget_investment, :with_administrator, budget: budget)
        create(:budget_investment, budget: budget)

        expect(Budget::Investment.scoped_filter(params, "all")).to eq([investment1])
      end
    end

    describe "with valuation_finished filter" do
      let(:params) { { advanced_filters: ["valuation_finished"], budget_id: budget.id } }
      it "returns only investment with valuation finished" do
        investment1 = create(:budget_investment, :selected, budget: budget)
        create(:budget_investment, :with_administrator, budget: budget)
        create(:budget_investment, budget: budget)

        expect(Budget::Investment.scoped_filter(params, "all")).to eq([investment1])
      end
    end

    describe "with winners filter" do
      let(:params) { { advanced_filters: ["winners"], budget_id: budget.id } }
      it "returns only investment winners" do
        investment1 = create(:budget_investment, :winner, :finished, budget: budget)
        create(:budget_investment, :with_administrator, budget: budget)
        create(:budget_investment, budget: budget)

        expect(Budget::Investment.scoped_filter(params, "all")).to eq([investment1])
      end
    end
  end

  describe "scopes" do
    describe "valuation_open" do
      it "returns investments with valuation open" do
        investment = create(:budget_investment, valuation_finished: false)

        expect(Budget::Investment.valuation_open).to eq [investment]
      end

      it "does not return investments with valuation finished" do
        create(:budget_investment, valuation_finished: true)

        expect(Budget::Investment.valuation_open).to be_empty
      end
    end

    describe "valuation_finished" do
      it "returns investments with valuation finished" do
        investment = create(:budget_investment, valuation_finished: true)

        expect(Budget::Investment.valuation_finished).to eq [investment]
      end

      it "does not return investments with valuation open" do
        create(:budget_investment, valuation_finished: false)

        expect(Budget::Investment.valuation_finished).to be_empty
      end
    end

    describe "without_admin" do
      it "returns investments without an admin" do
        investment = create(:budget_investment, :finished, administrator: nil)

        expect(Budget::Investment.without_admin).to eq [investment]
      end

      it "does not return investments with an admin" do
        create(:budget_investment, :with_administrator)

        expect(Budget::Investment.without_admin).to be_empty
      end
    end

    describe "managed" do
      it "returns open investments with assigned admin but without assigned valuators" do
        investment = create(:budget_investment, :with_administrator)

        expect(Budget::Investment.managed).to eq [investment]
      end

      it "does not return investments without assigned admin" do
        create(:budget_investment, administrator: nil)

        expect(Budget::Investment.managed).to be_empty
      end

      it "does not return investments with assigned valuator" do
        create(:budget_investment, :with_administrator, :with_valuator)

        expect(Budget::Investment.managed).to be_empty
      end

      it "does not return finished investments" do
        create(:budget_investment, :with_administrator, :finished)

        expect(Budget::Investment.managed).to be_empty
      end
    end

    describe "valuating" do
      it "returns investments with assigned valuator but valuation not finished" do
        investment = create(:budget_investment, :open, :with_valuator)

        expect(Budget::Investment.valuating).to eq [investment]
      end

      it "returns investments with assigned valuator groups but valuation not finished" do
        investment = create(:budget_investment, :open, valuator_groups: [create(:valuator_group)])

        expect(Budget::Investment.valuating).to eq [investment]
      end

      it "does not return investments with valuation finished" do
        create(:budget_investment, :finished, :with_valuator)
        create(:budget_investment, :finished, valuator_groups: [create(:valuator_group)])

        expect(Budget::Investment.valuating).to be_empty
      end

      it "does not return investments without valuator nor valuator group" do
        create(:budget_investment, :open)

        expect(Budget::Investment.valuating).to be_empty
      end
    end

    describe "feasible" do
      it "returns feasible investments" do
        feasible_investment = create(:budget_investment, :feasible)

        expect(Budget::Investment.feasible).to eq [feasible_investment]
      end

      it "does not return unfeasible nor undecided investments" do
        create(:budget_investment, :undecided)
        create(:budget_investment, :unfeasible)

        expect(Budget::Investment.feasible).to be_empty
      end
    end

    describe "unfeasible" do
      it "returns unfeasible investments" do
        unfeasible_investment = create(:budget_investment, :unfeasible)

        expect(Budget::Investment.unfeasible).to eq [unfeasible_investment]
      end

      it "does not return feasible nor undecided investments" do
        create(:budget_investment, :feasible)
        create(:budget_investment, :undecided)

        expect(Budget::Investment.unfeasible).to be_empty
      end
    end

    describe "not_unfeasible" do
      it "returns feasible and undecided investments" do
        undecided_investment = create(:budget_investment, :undecided)
        feasible_investment = create(:budget_investment, :feasible)

        expect(Budget::Investment.not_unfeasible).to match_array [undecided_investment, feasible_investment]
      end

      it "does not return unfeasible investments" do
        create(:budget_investment, :unfeasible)

        expect(Budget::Investment.not_unfeasible).to be_empty
      end
    end

    describe "undecided" do
      it "returns undecided investments" do
        undecided_investment = create(:budget_investment, :undecided)

        expect(Budget::Investment.undecided).to eq [undecided_investment]
      end

      it "does not return feasible nor unfeasible investments" do
        create(:budget_investment, :feasible)
        create(:budget_investment, :unfeasible)

        expect(Budget::Investment.undecided).to be_empty
      end
    end

    describe "selected" do
      it "returns selected investments" do
        selected_investment = create(:budget_investment, :selected)

        expect(Budget::Investment.selected).to eq [selected_investment]
      end

      it "does not return unselected investments" do
        create(:budget_investment, :unselected)

        expect(Budget::Investment.selected).to be_empty
      end
    end

    describe "unselected" do
      it "returns all unselected not_unfeasible investments" do
        unselected_undecided_investment = create(:budget_investment, :unselected, :undecided)
        unselected_feasible_investment = create(:budget_investment, :unselected, :feasible)

        expect(Budget::Investment.unselected).to match_array [unselected_undecided_investment,
                                                              unselected_feasible_investment]
      end

      it "does not return selected investments" do
        create(:budget_investment, :selected)

        expect(Budget::Investment.unselected).to be_empty
      end

      it "does not return unfeasible investments" do
        create(:budget_investment, :unselected, :unfeasible)

        expect(Budget::Investment.unselected).to be_empty
      end
    end

    describe "sort_by_title" do
      it "sorts using the title in the current locale" do
        create(:budget_investment,
               title_en: "CCCC",
               title_es: "BBBB",
               description_en: "CCCC",
               description_es: "BBBB")

        create(:budget_investment,
               title_en: "DDDD",
               title_es: "AAAA",
               description_en: "DDDD",
               description_es: "AAAA")

        expect(Budget::Investment.sort_by_title.map(&:title)).to eq %w[CCCC DDDD]
      end

      it "takes into consideration title fallbacks when there is no translation for current locale" do
        create(:budget_investment, title: "BBBB")
        I18n.with_locale(:es) do
          create(:budget_investment, title: "AAAA")
        end

        expect(Budget::Investment.sort_by_title.map(&:title)).to eq %w[AAAA BBBB]
      end
    end

    describe ".sort_by_ballot_lines" do
      let(:budget) { create(:budget, :balloting) }
      let(:ballot) { create(:budget_ballot, user: create(:user), budget: budget) }

      it "adjusts results when investments are added and removed" do
        letter_a = create(:budget_investment, :selected, budget: budget, title: "A letter")
        letter_b = create(:budget_investment, :selected, budget: budget, title: "B letter")
        letter_c = create(:budget_investment, :selected, budget: budget, title: "C letter")

        ballot.add_investment(letter_b)
        ballot.add_investment(letter_a)
        ballot.add_investment(letter_c)

        ballot.investments.delete(letter_a)
        ballot.add_investment(letter_a)

        ordered_investments = ballot.investments.sort_by_ballot_lines

        expect(ordered_investments.map(&:title)).to eq ["B letter", "C letter", "A letter"]
      end

      it "does not sort alphabetically" do
        ["B letter", "A letter", "C letter"].each do |title|
          ballot.add_investment(create(:budget_investment, :selected, budget: budget, title: title))
        end

        ordered_investments = ballot.investments.sort_by_ballot_lines

        expect(ordered_investments.map(&:title)).to eq ["B letter", "A letter", "C letter"]
      end

      it "does not sort by price" do
        [2, 1, 3].each do |price|
          ballot.add_investment(create(:budget_investment, :selected, budget: budget, price: price))
        end

        ordered_investments = ballot.investments.sort_by_ballot_lines

        expect(ordered_investments.map(&:price)).to eq [2, 1, 3]
      end
    end

    describe "search_by_title_or_id" do
      it "does not return investments by description" do
        create(:budget_investment, title: "Something", description: "Awesome")

        expect(Budget::Investment.search_by_title_or_id("Awesome")).to be_empty
      end

      it "returns investment by given id" do
        investment = create(:budget_investment)

        expect(Budget::Investment.search_by_title_or_id(investment.id.to_s)).to eq([investment])
      end

      it "returns investments by given title" do
        investment = create(:budget_investment, title: "Investment title")

        expect(Budget::Investment.search_by_title_or_id("Investment title")).to eq([investment])
      end

      it "finds investments with numbers in their title" do
        investment = create(:budget_investment, title: "99 red balloons")

        expect(Budget::Investment.search_by_title_or_id("99")).to eq([investment])
      end
    end
  end

  describe "apply_filters_and_search" do
    let(:budget) { create(:budget) }

    it "returns feasible investments" do
      investment1 = create(:budget_investment, :feasible,   budget: budget)
      investment2 = create(:budget_investment, :feasible,   budget: budget)
      investment3 = create(:budget_investment, :unfeasible, budget: budget)

      results = Budget::Investment.apply_filters_and_search(budget, {}, :feasible)

      expect(results).to     include investment1
      expect(results).to     include investment2
      expect(results).not_to include investment3
    end

    it "returns unfeasible investments" do
      investment1 = create(:budget_investment, :unfeasible, budget: budget)
      investment2 = create(:budget_investment, :unfeasible, budget: budget)
      investment3 = create(:budget_investment, :feasible,   budget: budget)

      results = Budget::Investment.apply_filters_and_search(budget, {}, :unfeasible)

      expect(results).to     include investment1
      expect(results).to     include investment2
      expect(results).not_to include investment3
    end

    it "returns selected investments" do
      budget.update!(phase: "balloting")

      investment1 = create(:budget_investment, :feasible, :selected,   budget: budget)
      investment2 = create(:budget_investment, :feasible, :selected,   budget: budget)
      investment3 = create(:budget_investment, :feasible, :unselected, budget: budget)

      results = Budget::Investment.apply_filters_and_search(budget, {}, :selected)

      expect(results).to     include investment1
      expect(results).to     include investment2
      expect(results).not_to include investment3
    end

    it "returns unselected investments" do
      budget.update!(phase: "balloting")

      investment1 = create(:budget_investment, :feasible, :unselected, budget: budget)
      investment2 = create(:budget_investment, :feasible, :unselected, budget: budget)
      investment3 = create(:budget_investment, :feasible, :selected,   budget: budget)

      results = Budget::Investment.apply_filters_and_search(budget, {}, :unselected)

      expect(results).to     include investment1
      expect(results).to     include investment2
      expect(results).not_to include investment3
    end

    it "returns investmens by heading" do
      group = create(:budget_group, budget: budget)

      heading1 = create(:budget_heading, group: group)
      heading2 = create(:budget_heading, group: group)

      investment1 = create(:budget_investment, heading: heading1)
      investment2 = create(:budget_investment, heading: heading1)
      investment3 = create(:budget_investment, heading: heading2)

      results = Budget::Investment.apply_filters_and_search(budget, heading_id: heading1.id)

      expect(results).to     include investment1
      expect(results).to     include investment2
      expect(results).not_to include investment3
    end

    it "returns investments by search string" do
      investment1 = create(:budget_investment, title: "health for all",  budget: budget)
      investment2 = create(:budget_investment, title: "improved health", budget: budget)
      investment3 = create(:budget_investment, title: "finance",         budget: budget)

      results = Budget::Investment.apply_filters_and_search(budget, search: "health")

      expect(results).to match_array [investment1, investment2]
      expect(results).not_to include investment3
    end
  end

  describe "search" do
    context "attributes" do
      let(:attributes) do
        { title: "save the world",
          description: "in order to save the world one must think about...",
          title_es: "para salvar el mundo uno debe pensar en...",
          description_es: "uno debe pensar" }
      end

      it "searches by title" do
        budget_investment = create(:budget_investment, attributes)
        results = Budget::Investment.search("save the world")
        expect(results).to eq([budget_investment])
      end

      it "searches by title across all languages" do
        budget_investment = create(:budget_investment, attributes)
        results = Budget::Investment.search("salvar el mundo")
        expect(results).to eq([budget_investment])
      end

      it "searches by author name" do
        author = create(:user, username: "Danny Trejo")
        budget_investment = create(:budget_investment, author: author)
        results = Budget::Investment.search("Danny")
        expect(results).to eq([budget_investment])
      end
    end

    context "tags" do
      it "searches by tags" do
        investment = create(:budget_investment, tag_list: "Latina")

        results = Budget::Investment.search("Latina")
        expect(results.first).to eq(investment)

        results = Budget::Investment.search("Latin")
        expect(results.first).to eq(investment)
      end

      it "gets and sets valuation tags through virtual attributes" do
        investment = create(:budget_investment)

        investment.valuation_tag_list = %w[Code Test Refactor]

        expect(investment.valuation_tag_list).to match_array(%w[Code Test Refactor])
      end

      describe ".by_tag" do
        it "does not return duplicate records for tags in different contexts" do
          investment = create(:budget_investment, tag_list: ["Same"], valuation_tag_list: ["Same"])

          expect(Budget::Investment.by_tag("Same")).to eq [investment]
        end
      end
    end
  end

  describe "Permissions" do
    let(:budget)      { create(:budget) }
    let(:group)       { create(:budget_group, budget: budget) }
    let(:heading)     { create(:budget_heading, group: group) }
    let(:user)        { create(:user, :level_two) }
    let(:luser)       { create(:user) }
    let(:district_sp) { create(:budget_investment, budget: budget, heading: heading) }

    describe "#reason_for_not_being_selectable_by" do
      it "rejects not logged in users" do
        expect(district_sp.reason_for_not_being_selectable_by(nil)).to eq(:not_logged_in)
      end

      it "rejects not verified users" do
        expect(district_sp.reason_for_not_being_selectable_by(luser)).to eq(:not_verified)
      end

      it "rejects organizations" do
        create(:organization, user: user)
        expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:organization)
      end

      it "rejects selections when selecting is not allowed (via admin setting)" do
        budget.phase = "reviewing"
        expect(district_sp.reason_for_not_being_selectable_by(user)).to eq(:no_selecting_allowed)
      end

      it "accepts valid selections when selecting is allowed" do
        budget.phase = "selecting"
        expect(district_sp.reason_for_not_being_selectable_by(user)).to be nil
      end

      it "rejects votes in two headings of the same group" do
        carabanchel = create(:budget_heading, group: group)
        salamanca   = create(:budget_heading, group: group)

        create(:budget_investment, heading: carabanchel, voters: [user])

        salamanca_investment = create(:budget_investment, heading: salamanca)

        expect(salamanca_investment.valid_heading?(user)).to be false
      end

      it "accepts votes in multiple headings of the same group" do
        group.update!(max_votable_headings: 2)
        carabanchel = create(:budget_heading, group: group)
        salamanca   = create(:budget_heading, group: group)

        create(:budget_investment, heading: carabanchel, voters: [user])

        salamanca_investment = create(:budget_investment, heading: salamanca)

        expect(salamanca_investment.valid_heading?(user)).to be true
      end

      it "accepts votes in any heading previously voted in" do
        group.update!(max_votable_headings: 2)

        carabanchel = create(:budget_heading, group: group)
        salamanca   = create(:budget_heading, group: group)

        carabanchel_investment = create(:budget_investment, heading: carabanchel, voters: [user])
        salamanca_investment   = create(:budget_investment, heading: salamanca, voters: [user])

        expect(carabanchel_investment.valid_heading?(user)).to be true
        expect(salamanca_investment.valid_heading?(user)).to be true
      end

      it "allows votes in a group with a single heading" do
        all_city_investment = create(:budget_investment, heading: heading)

        expect(all_city_investment.valid_heading?(user)).to be true
      end

      it "allows votes in a group with a single heading after voting in that heading" do
        create(:budget_investment, heading: heading, voters: [user])

        investment_for_same_heading = create(:budget_investment, heading: heading)

        expect(investment_for_same_heading.valid_heading?(user)).to be true
      end

      it "allows votes in a group with a single heading after voting in another group" do
        districts = create(:budget_group, budget: budget)
        carabanchel = create(:budget_heading, group: districts)

        create(:budget_investment, heading: carabanchel, voters: [user])

        investment_from_different_group = create(:budget_investment, heading: heading)

        expect(investment_from_different_group.valid_heading?(user)).to be true
      end

      it "allows votes in a group with multiple headings after voting in group with a single heading" do
        districts = create(:budget_group, budget: budget)
        2.times { create(:budget_heading, group: districts) }

        create(:budget_investment, heading: heading, voters: [user])

        investment = create(:budget_investment, heading: districts.headings.sample)

        expect(investment.valid_heading?(user)).to be true
      end

      describe "#can_vote_in_another_heading?" do
        let(:districts)   { create(:budget_group, budget: budget) }
        let(:carabanchel) { create(:budget_heading, group: districts) }
        let(:salamanca)   { create(:budget_heading, group: districts) }
        let(:latina)      { create(:budget_heading, group: districts) }

        let(:carabanchel_investment) { create(:budget_investment, heading: carabanchel) }
        let(:salamanca_investment)   { create(:budget_investment, heading: salamanca) }
        let(:latina_investment)      { create(:budget_investment, heading: latina) }

        it "returns true if the user has voted in less headings than the maximum" do
          districts.update!(max_votable_headings: 2)

          create(:vote, votable: carabanchel_investment, voter: user)

          expect(salamanca_investment.can_vote_in_another_heading?(user)).to be true
        end

        it "returns false if the user has already voted in the maximum number of headings" do
          districts.update!(max_votable_headings: 2)

          create(:vote, votable: carabanchel_investment, voter: user)
          create(:vote, votable: salamanca_investment, voter: user)

          expect(latina_investment.can_vote_in_another_heading?(user)).to be false
        end
      end
    end
  end

  describe "#voted_in?" do
    let(:user) { create(:user) }
    let(:investment) { create(:budget_investment) }

    it "returns true if the user has voted in this heading" do
      create(:vote, votable: investment, voter: user)

      expect(investment.voted_in?(investment.heading, user)).to be true
    end

    it "returns false if the user has not voted in this heading" do
      expect(investment.voted_in?(investment.heading, user)).to be false
    end
  end

  describe "Order" do
    describe "#sort_by_confidence_score" do
      it "orders by confidence_score" do
        least_voted = create(:budget_investment, cached_votes_up: 1)
        most_voted = create(:budget_investment, cached_votes_up: 10)
        some_votes = create(:budget_investment, cached_votes_up: 5)

        expect(Budget::Investment.sort_by_confidence_score).to eq [most_voted, some_votes, least_voted]
      end

      it "orders by confidence_score and then by id" do
        least_voted  = create(:budget_investment, cached_votes_up: 1)
        most_voted   = create(:budget_investment, cached_votes_up: 10)
        most_voted2  = create(:budget_investment, cached_votes_up: 10)
        least_voted2 = create(:budget_investment, cached_votes_up: 1)

        expect(Budget::Investment.sort_by_confidence_score).to eq [
          most_voted2, most_voted, least_voted2, least_voted
        ]
      end
    end
  end

  describe "responsible_name" do
    let(:user) { create(:user, document_number: "123456") }
    let!(:investment) { create(:budget_investment, author: user) }

    it "gets updated with the document_number" do
      expect(investment.responsible_name).to eq("123456")
    end

    it "does not get updated if the user is erased" do
      user.erase
      user.update!(document_number: nil)
      expect(user.document_number).to be_blank
      investment.valid?
      expect(investment.responsible_name).to eq("123456")
    end
  end

  describe "total votes" do
    it "takes into account physical votes in addition to web votes" do
      budget = create(:budget, :selecting)
      investment = create(:budget_investment, budget: budget)

      investment.register_selection(create(:user, :level_two))
      expect(investment.total_votes).to eq(1)

      investment.physical_votes = 10
      expect(investment.total_votes).to eq(11)
    end
  end

  describe "#with_supports" do
    it "returns proposals with supports" do
      inv1 = create(:budget_investment, voters: [create(:user)])
      inv2 = create(:budget_investment)

      expect(Budget::Investment.with_supports).to eq [inv1]
      expect(Budget::Investment.with_supports).not_to include(inv2)
    end
  end

  describe "Final Voting" do
    describe "Permissions" do
      let(:budget)      { create(:budget) }
      let(:heading)     { create(:budget_heading, budget: budget) }
      let(:user)        { create(:user, :level_two) }
      let(:luser)       { create(:user) }
      let(:ballot)      { create(:budget_ballot, budget: budget) }
      let(:investment)  { create(:budget_investment, :selected, budget: budget, heading: heading) }

      describe "#reason_for_not_being_ballotable_by" do
        it "rejects not logged in users" do
          expect(investment.reason_for_not_being_ballotable_by(nil, ballot)).to eq(:not_logged_in)
        end

        it "rejects not verified users" do
          expect(investment.reason_for_not_being_ballotable_by(luser, ballot)).to eq(:not_verified)
        end

        it "rejects organizations" do
          create(:organization, user: user)
          expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to eq(:organization)
        end

        it "rejects votes when voting is not allowed (wrong phase)" do
          budget.phase = "reviewing"
          expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to eq(:no_ballots_allowed)
        end

        it "rejects non-selected investments" do
          investment.selected = false
          expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_selected)
        end

        it "accepts valid ballots when voting is allowed" do
          budget.phase = "balloting"
          expect(investment.reason_for_not_being_ballotable_by(user, ballot)).to be nil
        end

        it "accepts valid selections" do
          budget.phase = "selecting"
          expect(investment.reason_for_not_being_selectable_by(user)).to be nil
        end

        it "rejects users with different headings" do
          budget.phase = "balloting"
          group = create(:budget_group, budget: budget)
          california = create(:budget_heading, group: group)
          new_york = create(:budget_heading, group: group)

          inv1 = create(:budget_investment, :selected, budget: budget, heading: california)
          inv2 = create(:budget_investment, :selected, budget: budget, heading: new_york)
          ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])

          expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:different_heading_assigned)
        end

        it "rejects proposals with price higher than current available money" do
          budget.phase = "balloting"
          districts = create(:budget_group, budget: budget)
          carabanchel = create(:budget_heading, group: districts, price: 35)
          inv1 = create(:budget_investment, :selected, budget: budget, heading: carabanchel, price: 30)
          inv2 = create(:budget_investment, :selected, budget: budget, heading: carabanchel, price: 10)

          ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])

          expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money)
        end

        context "Approval voting" do
          before { budget.update!(phase: "balloting", voting_style: "approval") }
          let(:group) { create(:budget_group, budget: budget) }

          it "does not reject investments based on available money" do
            heading = create(:budget_heading, group: group, max_ballot_lines: 2)
            inv1 = create(:budget_investment, :selected, heading: heading, price: heading.price)
            inv2 = create(:budget_investment, :selected, heading: heading, price: heading.price)
            ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])

            expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to be nil
          end

          it "rejects if not enough available votes" do
            heading = create(:budget_heading, group: group, max_ballot_lines: 1)
            inv1 = create(:budget_investment, :selected, heading: heading)
            inv2 = create(:budget_investment, :selected, heading: heading)
            ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])

            reason = inv2.reason_for_not_being_ballotable_by(user, ballot)

            expect(reason).to eq(:not_enough_available_votes)
          end
        end
      end
    end
  end

  describe "Reclassification" do
    let(:budget)   { create(:budget, :balloting)   }
    let(:group)    { create(:budget_group, budget: budget) }
    let(:heading1) { create(:budget_heading, group: group) }
    let(:heading2) { create(:budget_heading, group: group) }

    describe "saved_change_to_heading?" do
      it "returns true if budget is in balloting phase and heading has changed" do
        investment = create(:budget_investment, heading: heading1)
        investment.update!(heading: heading2)

        expect(investment.saved_change_to_heading?).to be true
      end

      it "returns false if heading has not changed" do
        investment = create(:budget_investment, heading: heading1)
        investment.update!(heading: heading1)

        expect(investment.saved_change_to_heading?).to be false
      end

      it "returns false if budget is not balloting phase" do
        Budget::Phase::PHASE_KINDS.excluding("balloting").each do |phase|
          budget.update!(phase: phase)
          investment = create(:budget_investment, heading: heading1)

          investment.update!(heading: heading2)

          expect(investment.saved_change_to_heading?).to be false
        end
      end
    end

    describe "log_heading_change" do
      it "stores the previous heading before being reclassified" do
        investment = create(:budget_investment, heading: heading1)

        expect(investment.heading_id).to eq(heading1.id)
        expect(investment.previous_heading_id).to be nil

        investment.heading = heading2
        investment.save!

        investment.reload
        expect(investment.heading_id).to eq(heading2.id)
        expect(investment.previous_heading_id).to eq(heading1.id)
      end
    end

    describe "store_reclassified_votes" do
      it "stores the votes for a reclassified investment" do
        investment = create(:budget_investment, :selected, heading: heading1)

        3.times { create(:user, ballot_lines: [investment]) }

        expect(investment.ballot_lines_count).to eq(3)

        investment.heading = heading2
        investment.store_reclassified_votes("heading_changed")

        reclassified_vote = Budget::ReclassifiedVote.first

        expect(Budget::ReclassifiedVote.count).to eq(3)
        expect(reclassified_vote.investment_id).to eq(investment.id)
        expect(reclassified_vote.user_id).to eq(Budget::Ballot.first.user.id)
        expect(reclassified_vote.reason).to eq("heading_changed")
      end
    end

    describe "remove_reclassified_votes" do
      it "removes votes from invesment" do
        investment = create(:budget_investment, :selected, heading: heading1)

        3.times { create(:user, ballot_lines: [investment]) }

        expect(investment.ballot_lines_count).to eq(3)

        investment.heading = heading2
        investment.remove_reclassified_votes

        investment.reload
        expect(investment.ballot_lines_count).to eq(0)
      end
    end

    describe "check_for_reclassification" do
      it "removes votes and stores reclassfied votes if an investment has been reclassified" do
        investment = create(:budget_investment, :selected, heading: heading1)

        3.times { create(:user, ballot_lines: [investment]) }

        expect(investment.ballot_lines_count).to eq(3)

        investment.heading = heading2
        investment.save!
        investment.reload

        expect(investment.ballot_lines_count).to eq(0)
        expect(Budget::ReclassifiedVote.count).to eq(3)
      end

      it "does not remove votes nor store reclassified votes if the investment has not been reclassifed" do
        investment = create(:budget_investment, :selected, heading: heading1)

        3.times { create(:user, ballot_lines: [investment]) }

        expect(investment.ballot_lines_count).to eq(3)

        investment.save!
        investment.reload

        expect(investment.ballot_lines_count).to eq(3)
        expect(Budget::ReclassifiedVote.count).to eq(0)
      end
    end
  end

  describe "admin_and_valuator_users_associated" do
    let(:investment) { create(:budget_investment) }
    let(:valuator_group) { create(:valuator_group) }
    let(:valuator) { create(:valuator) }
    let(:administrator) { create(:administrator) }

    it "returns empty array if not valuators or administrator assigned" do
      expect(investment.admin_and_valuator_users_associated).to eq([])
    end

    it "returns all valuator and administrator users" do
      valuator_group.valuators << valuator
      investment.valuator_groups << valuator_group
      expect(investment.admin_and_valuator_users_associated).to eq([valuator])
      investment.administrator = administrator
      expect(investment.admin_and_valuator_users_associated).to eq([valuator, administrator])
    end

    it "returns uniq valuators or administrator users" do
      valuator_group.valuators << valuator
      investment.valuator_groups << valuator_group
      investment.valuators << valuator
      investment.administrator = administrator
      expect(investment.admin_and_valuator_users_associated).to eq([valuator, administrator])
    end
  end

  describe "milestone_tags" do
    context "without milestone_tags" do
      let(:investment) { create(:budget_investment) }

      it "do not have milestone_tags" do
        expect(investment.milestone_tag_list).to eq([])
        expect(investment.milestone_tags).to eq([])
      end

      it "add a new milestone_tag" do
        investment.milestone_tag_list = "tag1,tag2"

        expect(investment.milestone_tag_list).to eq(["tag1", "tag2"])
      end
    end

    context "with milestone_tags" do
      let(:investment) { create(:budget_investment, :with_milestone_tags) }

      it "has milestone_tags" do
        expect(investment.reload.milestone_tag_list.count).to eq(1)
      end
    end
  end
end