Having exceptions is better than having silent bugs.
There are a few methods I've kept the same way they were.
The `RelatedContentScore#score_with_opposite` method is a bit peculiar:
it creates scores for both itself and the opposite related content,
which means the opposite related content will try to create the same
scores as well.
We've already got a test to check `Budget::Ballot#add_investment` when
creating a line fails ("Edge case voting a non-elegible investment").
Finally, the method `User#send_oauth_confirmation_instructions` doesn't
update the record when the email address isn't already present, leading
to the test "Try to register with the email of an already existing user,
when an unconfirmed email was provided by oauth" fo fail if we raise an
exception for an invalid user. That's because updating a user's email
doesn't update the database automatically, but instead a confirmation
email is sent.
There are also a few false positives for classes which don't have bang
methods (like the GraphQL classes) or destroying attachments.
For these reasons, I'm adding the rule with a "Refactor" severity,
meaning it's a rule we can break if necessary.
1094 lines
34 KiB
Ruby
1094 lines
34 KiB
Ruby
# coding: utf-8
|
|
require "rails_helper"
|
|
|
|
describe Proposal do
|
|
let(:proposal) { build(:proposal) }
|
|
|
|
describe "Concerns" do
|
|
it_behaves_like "has_public_author"
|
|
it_behaves_like "notifiable"
|
|
it_behaves_like "map validations"
|
|
it_behaves_like "globalizable", :retired_proposal
|
|
it_behaves_like "sanitizable"
|
|
it_behaves_like "acts as paranoid", :proposal
|
|
end
|
|
|
|
it "is valid" do
|
|
expect(proposal).to be_valid
|
|
end
|
|
|
|
it "is not valid without an author" do
|
|
proposal.author = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid without a summary" do
|
|
proposal.summary = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
describe "#title" do
|
|
it "is not valid without a title" do
|
|
proposal.title = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid when very short" do
|
|
proposal.title = "abc"
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid when very long" do
|
|
proposal.title = "a" * 81
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "#description" do
|
|
it "is not valid when very long" do
|
|
proposal.description = "a" * 6001
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "#video_url" do
|
|
it "is not valid when URL is not from Youtube or Vimeo" do
|
|
proposal.video_url = "https://twitter.com"
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is valid when URL is from Youtube or Vimeo" do
|
|
proposal.video_url = "https://vimeo.com/112681885"
|
|
expect(proposal).to be_valid
|
|
end
|
|
end
|
|
|
|
describe "#responsible_name" do
|
|
it "is mandatory" do
|
|
proposal.responsible_name = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid when very short" do
|
|
proposal.responsible_name = "abc"
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid when very long" do
|
|
proposal.responsible_name = "a" * 61
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is the document_number if level two user" do
|
|
author = create(:user, :level_two, document_number: "12345678Z")
|
|
proposal.author = author
|
|
proposal.responsible_name = nil
|
|
|
|
expect(proposal).to be_valid
|
|
proposal.responsible_name = "12345678Z"
|
|
end
|
|
|
|
it "is the document_number if level three user" do
|
|
author = create(:user, :level_three, document_number: "12345678Z")
|
|
proposal.author = author
|
|
proposal.responsible_name = nil
|
|
|
|
expect(proposal).to be_valid
|
|
proposal.responsible_name = "12345678Z"
|
|
end
|
|
|
|
it "is not updated when the author is deleted" do
|
|
author = create(:user, :level_three, document_number: "12345678Z")
|
|
proposal.author = author
|
|
proposal.save!
|
|
|
|
proposal.author.erase
|
|
|
|
proposal.save!
|
|
expect(proposal.responsible_name).to eq "12345678Z"
|
|
end
|
|
end
|
|
|
|
describe "tag_list" do
|
|
it "is not valid with a tag list of more than 6 elements" do
|
|
proposal.tag_list = ["Hacienda", "Economía", "Medio Ambiente", "Corrupción", "Fiestas populares", "Prensa", "Huelgas"]
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is valid with a tag list of up to 6 elements" do
|
|
proposal.tag_list = ["Hacienda", "Economía", "Medio Ambiente", "Corrupción", "Fiestas populares", "Prensa"]
|
|
expect(proposal).to be_valid
|
|
end
|
|
end
|
|
|
|
it "is not valid without accepting terms of service" do
|
|
proposal.terms_of_service = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "has a code" do
|
|
Setting["proposal_code_prefix"] = "TEST"
|
|
proposal = create(:proposal)
|
|
expect(proposal.code).to eq "TEST-#{proposal.created_at.strftime('%Y-%m')}-#{proposal.id}"
|
|
|
|
Setting["proposal_code_prefix"] = "MAD"
|
|
end
|
|
|
|
describe "#retired_explanation" do
|
|
it "is valid when retired timestamp is present and retired explanation is defined" do
|
|
proposal.retired_at = Time.current
|
|
proposal.retired_explanation = "Duplicated of ..."
|
|
proposal.retired_reason = "duplicated"
|
|
expect(proposal).to be_valid
|
|
end
|
|
|
|
it "is not valid when retired_at is present and retired explanation is empty" do
|
|
proposal.retired_at = Time.current
|
|
proposal.retired_explanation = nil
|
|
proposal.retired_reason = "duplicated"
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "#retired_reason" do
|
|
it "is valid when retired timestamp is present and retired reason is defined" do
|
|
proposal.retired_at = Time.current
|
|
proposal.retired_explanation = "Duplicated of ..."
|
|
proposal.retired_reason = "duplicated"
|
|
expect(proposal).to be_valid
|
|
end
|
|
|
|
it "is not valid when retired timestamp is present but defined retired reason
|
|
is not included in retired reasons" do
|
|
proposal.retired_at = Time.current
|
|
proposal.retired_explanation = "Duplicated of ..."
|
|
proposal.retired_reason = "duplicate"
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
|
|
it "is not valid when retired_at is present and retired reason is empty" do
|
|
proposal.retired_at = Time.current
|
|
proposal.retired_explanation = "Duplicated of ..."
|
|
proposal.retired_reason = nil
|
|
expect(proposal).not_to be_valid
|
|
end
|
|
end
|
|
|
|
describe "#editable?" do
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
before { Setting["max_votes_for_proposal_edit"] = 5 }
|
|
|
|
it "is true if proposal has no votes yet" do
|
|
expect(proposal.total_votes).to eq(0)
|
|
expect(proposal.editable?).to be true
|
|
end
|
|
|
|
it "is true if proposal has less than limit votes" do
|
|
create_list(:vote, 4, votable: proposal)
|
|
expect(proposal.total_votes).to eq(4)
|
|
expect(proposal.editable?).to be true
|
|
end
|
|
|
|
it "is false if proposal has more than limit votes" do
|
|
create_list(:vote, 6, votable: proposal)
|
|
expect(proposal.total_votes).to eq(6)
|
|
expect(proposal.editable?).to be false
|
|
end
|
|
end
|
|
|
|
describe "#votable_by?" do
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
it "is true for level two verified users" do
|
|
user = create(:user, residence_verified_at: Time.current, confirmed_phone: "666333111")
|
|
expect(proposal.votable_by?(user)).to be true
|
|
end
|
|
|
|
it "is true for level three verified users" do
|
|
user = create(:user, verified_at: Time.current)
|
|
expect(proposal.votable_by?(user)).to be true
|
|
end
|
|
|
|
it "is false for anonymous users" do
|
|
user = create(:user)
|
|
expect(proposal.votable_by?(user)).to be false
|
|
end
|
|
end
|
|
|
|
describe "#register_vote" do
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
describe "from level two verified users" do
|
|
it "registers vote" do
|
|
user = create(:user, residence_verified_at: Time.current, confirmed_phone: "666333111")
|
|
expect { proposal.register_vote(user, "yes") }.to change { proposal.reload.votes_for.size }.by(1)
|
|
end
|
|
end
|
|
|
|
describe "from level three verified users" do
|
|
it "registers vote" do
|
|
user = create(:user, verified_at: Time.current)
|
|
expect { proposal.register_vote(user, "yes") }.to change { proposal.reload.votes_for.size }.by(1)
|
|
end
|
|
end
|
|
|
|
describe "from anonymous users" do
|
|
it "does not register vote" do
|
|
user = create(:user)
|
|
expect { proposal.register_vote(user, "yes") }.to change { proposal.reload.votes_for.size }.by(0)
|
|
end
|
|
end
|
|
|
|
it "does not register vote for archived proposals" do
|
|
user = create(:user, verified_at: Time.current)
|
|
archived_proposal = create(:proposal, :archived)
|
|
|
|
expect { archived_proposal.register_vote(user, "yes") }.to change { proposal.reload.votes_for.size }.by(0)
|
|
end
|
|
end
|
|
|
|
describe "#cached_votes_up" do
|
|
|
|
describe "with deprecated long tag list" do
|
|
|
|
it "increases number of cached_total_votes" do
|
|
proposal = create(:proposal)
|
|
|
|
tag_list = ["tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7"]
|
|
proposal.update_attribute(:tag_list, tag_list)
|
|
|
|
expect(proposal.update_cached_votes).to eq(true)
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
describe "#hot_score" do
|
|
let(:now) { Time.current }
|
|
|
|
it "period is correctly calculated to get exact votes per day" do
|
|
new_proposal = create(:proposal, created_at: 23.hours.ago)
|
|
2.times { new_proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
expect(new_proposal.hot_score).to be 2
|
|
|
|
old_proposal = create(:proposal, created_at: 25.hours.ago)
|
|
2.times { old_proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
expect(old_proposal.hot_score).to be 1
|
|
|
|
older_proposal = create(:proposal, created_at: 49.hours.ago)
|
|
3.times { older_proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
expect(old_proposal.hot_score).to be 1
|
|
end
|
|
|
|
it "remains the same for not voted proposals" do
|
|
new = create(:proposal, created_at: now)
|
|
old = create(:proposal, created_at: 1.day.ago)
|
|
older = create(:proposal, created_at: 2.months.ago)
|
|
expect(new.hot_score).to be 0
|
|
expect(old.hot_score).to be 0
|
|
expect(older.hot_score).to be 0
|
|
end
|
|
|
|
it "increases for proposals with more positive votes" do
|
|
more_positive_votes = create(:proposal)
|
|
2.times { more_positive_votes.vote_by(voter: create(:user), vote: "yes") }
|
|
|
|
less_positive_votes = create(:proposal)
|
|
less_positive_votes.vote_by(voter: create(:user), vote: "yes")
|
|
|
|
expect(more_positive_votes.hot_score).to be > less_positive_votes.hot_score
|
|
end
|
|
|
|
it "increases for proposals with the same amount of positive votes within less days" do
|
|
newer_proposal = create(:proposal, created_at: now)
|
|
5.times { newer_proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
|
|
older_proposal = create(:proposal, created_at: 2.days.ago)
|
|
5.times { older_proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
|
|
expect(newer_proposal.hot_score).to be > older_proposal.hot_score
|
|
end
|
|
|
|
it "increases for proposals voted within the period (last month by default)" do
|
|
newer_proposal = create(:proposal, created_at: 2.months.ago)
|
|
20.times { create(:vote, votable: newer_proposal, created_at: 3.days.ago) }
|
|
|
|
older_proposal = create(:proposal, created_at: 2.months.ago)
|
|
20.times { create(:vote, votable: older_proposal, created_at: 40.days.ago) }
|
|
|
|
expect(newer_proposal.hot_score).to be > older_proposal.hot_score
|
|
end
|
|
|
|
describe "actions which affect it" do
|
|
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
before do
|
|
5.times { proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
2.times { proposal.vote_by(voter: create(:user), vote: "no") }
|
|
end
|
|
|
|
it "increases with positive votes" do
|
|
previous = proposal.hot_score
|
|
3.times { proposal.vote_by(voter: create(:user), vote: "yes") }
|
|
expect(previous).to be < proposal.hot_score
|
|
end
|
|
|
|
it "decreases with negative votes" do
|
|
previous = proposal.hot_score
|
|
3.times { proposal.vote_by(voter: create(:user), vote: "no") }
|
|
expect(previous).to be > proposal.hot_score
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "custom tag counters when hiding/restoring" do
|
|
it "decreases the tag counter when hiden, and increases it when restored" do
|
|
proposal = create(:proposal, tag_list: "foo")
|
|
tag = Tag.where(name: "foo").first
|
|
expect(tag.proposals_count).to eq(1)
|
|
|
|
proposal.hide
|
|
expect(tag.reload.proposals_count).to eq(0)
|
|
|
|
proposal.restore
|
|
expect(tag.reload.proposals_count).to eq(1)
|
|
end
|
|
end
|
|
|
|
describe "#confidence_score" do
|
|
|
|
it "takes into account votes" do
|
|
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 100)
|
|
expect(proposal.confidence_score).to eq(10000)
|
|
|
|
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 0)
|
|
expect(proposal.confidence_score).to eq(1)
|
|
|
|
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 75)
|
|
expect(proposal.confidence_score).to eq(7500)
|
|
|
|
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 750)
|
|
expect(proposal.confidence_score).to eq(75000)
|
|
|
|
proposal = create(:proposal, :with_confidence_score, cached_votes_up: 10)
|
|
expect(proposal.confidence_score).to eq(1000)
|
|
end
|
|
|
|
describe "actions which affect it" do
|
|
let(:proposal) { create(:proposal, :with_confidence_score) }
|
|
|
|
it "increases with like" do
|
|
previous = proposal.confidence_score
|
|
5.times { proposal.register_vote(create(:user, verified_at: Time.current), true) }
|
|
expect(previous).to be < proposal.confidence_score
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
describe "cache" do
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
it "expires cache when it has a new comment" do
|
|
expect { create(:comment, commentable: proposal) }
|
|
.to change { proposal.updated_at }
|
|
end
|
|
|
|
it "expires cache when it has a new vote" do
|
|
expect { create(:vote, votable: proposal) }
|
|
.to change { proposal.updated_at }
|
|
end
|
|
|
|
it "expires cache when it has a new flag" do
|
|
expect { create(:flag, flaggable: proposal) }
|
|
.to change { proposal.reload.updated_at }
|
|
end
|
|
|
|
it "expires cache when it has a new tag" do
|
|
expect { proposal.update(tag_list: "new tag") }
|
|
.to change { proposal.updated_at }
|
|
end
|
|
|
|
it "expires cache when hidden" do
|
|
expect { proposal.hide }
|
|
.to change { proposal.updated_at }
|
|
end
|
|
|
|
it "expires cache when the author is hidden" do
|
|
expect { proposal.author.hide }
|
|
.to change { [proposal.reload.updated_at, proposal.author.updated_at] }
|
|
end
|
|
|
|
it "expires cache when the author is erased" do
|
|
expect { proposal.author.erase }
|
|
.to change { [proposal.reload.updated_at, proposal.author.updated_at] }
|
|
end
|
|
|
|
it "expires cache when its author changes" do
|
|
expect { proposal.author.update(username: "Eva") }
|
|
.to change { [proposal.reload.updated_at, proposal.author.updated_at] }
|
|
end
|
|
|
|
it "expires cache when the author's organization get verified" do
|
|
create(:organization, user: proposal.author)
|
|
expect { proposal.author.organization.verify }
|
|
.to change { [proposal.reload.updated_at, proposal.author.updated_at] }
|
|
end
|
|
end
|
|
|
|
describe "voters" do
|
|
|
|
it "returns users that have voted for the proposal" do
|
|
proposal = create(:proposal)
|
|
voter1 = create(:user, :level_two, votables: [proposal])
|
|
voter2 = create(:user, :level_two, votables: [proposal])
|
|
voter3 = create(:user, :level_two)
|
|
|
|
expect(proposal.voters).to match_array [voter1, voter2]
|
|
expect(proposal.voters).not_to include(voter3)
|
|
end
|
|
|
|
it "does not return users that have been erased" do
|
|
proposal = create(:proposal)
|
|
voter1 = create(:user, :level_two, votables: [proposal])
|
|
voter2 = create(:user, :level_two, votables: [proposal])
|
|
|
|
voter2.erase
|
|
|
|
expect(proposal.voters).to eq [voter1]
|
|
end
|
|
|
|
it "does not return users that have been blocked" do
|
|
proposal = create(:proposal)
|
|
voter1 = create(:user, :level_two, votables: [proposal])
|
|
voter2 = create(:user, :level_two, votables: [proposal])
|
|
|
|
voter2.block
|
|
|
|
expect(proposal.voters).to eq [voter1]
|
|
end
|
|
|
|
end
|
|
|
|
describe "search" do
|
|
context "attributes" do
|
|
let(:attributes) do
|
|
{ title: "save the world",
|
|
summary: "basically",
|
|
description: "in order to save the world one must think about...",
|
|
title_es: "para salvar el mundo uno debe pensar en...",
|
|
summary_es: "basicamente",
|
|
description_es: "uno debe pensar" }
|
|
end
|
|
|
|
it "searches by title" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("save the world")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by title across all languages translations" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("salvar el mundo")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by summary" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("basically")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by summary across all languages translations" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("basicamente")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by description" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("one must think")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by description across all languages translations" do
|
|
proposal = create(:proposal, attributes)
|
|
results = Proposal.search("uno debe pensar")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by author name" do
|
|
author = create(:user, username: "Danny Trejo")
|
|
proposal = create(:proposal, author: author)
|
|
results = Proposal.search("Danny")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
it "searches by geozone" do
|
|
geozone = create(:geozone, name: "California")
|
|
proposal = create(:proposal, geozone: geozone)
|
|
results = Proposal.search("California")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
end
|
|
|
|
context "stemming" do
|
|
|
|
it "searches word stems" do
|
|
proposal = create(:proposal, summary: "Economía")
|
|
|
|
results = Proposal.search("economía")
|
|
expect(results).to eq([proposal])
|
|
|
|
results = Proposal.search("econo")
|
|
expect(results).to eq([proposal])
|
|
|
|
results = Proposal.search("eco")
|
|
expect(results).to eq([proposal])
|
|
end
|
|
|
|
end
|
|
|
|
context "accents" do
|
|
it "searches with accents" do
|
|
proposal = create(:proposal, summary: "difusión")
|
|
|
|
results = Proposal.search("difusion")
|
|
expect(results).to eq([proposal])
|
|
|
|
proposal2 = create(:proposal, summary: "estadisticas")
|
|
results = Proposal.search("estadísticas")
|
|
expect(results).to eq([proposal2])
|
|
|
|
proposal3 = create(:proposal, summary: "público")
|
|
results = Proposal.search("publico")
|
|
expect(results).to eq([proposal3])
|
|
end
|
|
end
|
|
|
|
context "case" do
|
|
it "searches case insensite" do
|
|
proposal = create(:proposal, title: "SHOUT")
|
|
|
|
results = Proposal.search("shout")
|
|
expect(results).to eq([proposal])
|
|
|
|
proposal2 = create(:proposal, title: "scream")
|
|
results = Proposal.search("SCREAM")
|
|
expect(results).to eq([proposal2])
|
|
end
|
|
end
|
|
|
|
context "tags" do
|
|
it "searches by tags" do
|
|
proposal = create(:proposal, tag_list: "Latina")
|
|
|
|
results = Proposal.search("Latina")
|
|
expect(results.first).to eq(proposal)
|
|
|
|
results = Proposal.search("Latin")
|
|
expect(results.first).to eq(proposal)
|
|
end
|
|
end
|
|
|
|
context "order" do
|
|
|
|
it "orders by weight" do
|
|
proposal_title = create(:proposal, title: "stop corruption")
|
|
proposal_description = create(:proposal, description: "stop corruption")
|
|
proposal_summary = create(:proposal, summary: "stop corruption")
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [proposal_title, proposal_summary, proposal_description]
|
|
end
|
|
|
|
it "orders by weight and then by votes" do
|
|
title_some_votes = create(:proposal, title: "stop corruption", cached_votes_up: 5)
|
|
title_least_voted = create(:proposal, title: "stop corruption", cached_votes_up: 2)
|
|
title_most_voted = create(:proposal, title: "stop corruption", cached_votes_up: 10)
|
|
|
|
summary_most_voted = create(:proposal, summary: "stop corruption", cached_votes_up: 10)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [title_most_voted, title_some_votes, title_least_voted, summary_most_voted]
|
|
end
|
|
|
|
it "gives much more weight to word matches than votes" do
|
|
exact_title_few_votes = create(:proposal, title: "stop corruption", cached_votes_up: 5)
|
|
similar_title_many_votes = create(:proposal, title: "stop some of the corruption", cached_votes_up: 500)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [exact_title_few_votes, similar_title_many_votes]
|
|
end
|
|
|
|
end
|
|
|
|
context "reorder" do
|
|
|
|
it "is able to reorder by hot_score after searching" do
|
|
lowest_score = create(:proposal, title: "stop corruption", cached_votes_up: 1)
|
|
highest_score = create(:proposal, title: "stop corruption", cached_votes_up: 2)
|
|
average_score = create(:proposal, title: "stop corruption", cached_votes_up: 3)
|
|
|
|
lowest_score.update_column(:hot_score, 1)
|
|
highest_score.update_column(:hot_score, 100)
|
|
average_score.update_column(:hot_score, 10)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [average_score, highest_score, lowest_score]
|
|
|
|
results = results.sort_by_hot_score
|
|
|
|
expect(results).to eq [highest_score, average_score, lowest_score]
|
|
end
|
|
|
|
it "is able to reorder by confidence_score after searching" do
|
|
lowest_score = create(:proposal, title: "stop corruption", cached_votes_up: 1)
|
|
highest_score = create(:proposal, title: "stop corruption", cached_votes_up: 2)
|
|
average_score = create(:proposal, title: "stop corruption", cached_votes_up: 3)
|
|
|
|
lowest_score.update_column(:confidence_score, 1)
|
|
highest_score.update_column(:confidence_score, 100)
|
|
average_score.update_column(:confidence_score, 10)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [average_score, highest_score, lowest_score]
|
|
|
|
results = results.sort_by_confidence_score
|
|
|
|
expect(results).to eq [highest_score, average_score, lowest_score]
|
|
end
|
|
|
|
it "is able to reorder by created_at after searching" do
|
|
recent = create(:proposal, title: "stop corruption", cached_votes_up: 1, created_at: 1.week.ago)
|
|
newest = create(:proposal, title: "stop corruption", cached_votes_up: 2, created_at: Time.current)
|
|
oldest = create(:proposal, title: "stop corruption", cached_votes_up: 3, created_at: 1.month.ago)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [oldest, newest, recent]
|
|
|
|
results = results.sort_by_created_at
|
|
|
|
expect(results).to eq [newest, recent, oldest]
|
|
end
|
|
|
|
it "is able to reorder by most commented after searching" do
|
|
least_commented = create(:proposal, title: "stop corruption", cached_votes_up: 1, comments_count: 1)
|
|
most_commented = create(:proposal, title: "stop corruption", cached_votes_up: 2, comments_count: 100)
|
|
some_comments = create(:proposal, title: "stop corruption", cached_votes_up: 3, comments_count: 10)
|
|
|
|
results = Proposal.search("stop corruption")
|
|
|
|
expect(results).to eq [some_comments, most_commented, least_commented]
|
|
|
|
results = results.sort_by_most_commented
|
|
|
|
expect(results).to eq [most_commented, some_comments, least_commented]
|
|
end
|
|
|
|
end
|
|
|
|
context "no results" do
|
|
|
|
it "no words match" do
|
|
create(:proposal, title: "save world")
|
|
|
|
results = Proposal.search("destroy planet")
|
|
expect(results).to eq([])
|
|
end
|
|
|
|
it "too many typos" do
|
|
create(:proposal, title: "fantastic")
|
|
|
|
results = Proposal.search("frantac")
|
|
expect(results).to eq([])
|
|
end
|
|
|
|
it "too much stemming" do
|
|
create(:proposal, title: "reloj")
|
|
|
|
results = Proposal.search("superrelojimetro")
|
|
expect(results).to eq([])
|
|
end
|
|
|
|
it "empty" do
|
|
create(:proposal, title: "great")
|
|
|
|
results = Proposal.search("")
|
|
expect(results).to eq([])
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
describe "#last_week" do
|
|
it "returns proposals created this week" do
|
|
proposal = create(:proposal)
|
|
|
|
expect(Proposal.last_week).to eq [proposal]
|
|
end
|
|
|
|
it "does not return proposals created more than a week ago" do
|
|
create(:proposal, created_at: 8.days.ago)
|
|
|
|
expect(Proposal.last_week).to be_empty
|
|
end
|
|
end
|
|
|
|
describe "for_summary" do
|
|
|
|
context "categories" do
|
|
|
|
it "returns proposals tagged with a category" do
|
|
create(:tag, :category, name: "culture")
|
|
proposal = create(:proposal, tag_list: "culture")
|
|
|
|
expect(Proposal.for_summary.values.flatten).to eq [proposal]
|
|
end
|
|
|
|
it "does not return proposals tagged without a category" do
|
|
create(:tag, :category, name: "culture")
|
|
create(:proposal, tag_list: "parks")
|
|
|
|
expect(Proposal.for_summary.values.flatten).to be_empty
|
|
end
|
|
end
|
|
|
|
context "districts" do
|
|
|
|
it "returns proposals with a geozone" do
|
|
california = create(:geozone, name: "california")
|
|
proposal = create(:proposal, geozone: california)
|
|
|
|
expect(Proposal.for_summary.values.flatten).to eq [proposal]
|
|
end
|
|
|
|
it "does not return proposals without a geozone" do
|
|
create(:geozone, name: "california")
|
|
create(:proposal)
|
|
|
|
expect(Proposal.for_summary.values.flatten).to be_empty
|
|
end
|
|
end
|
|
|
|
it "returns proposals created this week" do
|
|
create(:tag, :category, name: "culture")
|
|
proposal = create(:proposal, tag_list: "culture")
|
|
|
|
expect(Proposal.for_summary.values.flatten).to eq [proposal]
|
|
end
|
|
|
|
it "does not return proposals created more than a week ago" do
|
|
create(:tag, :category, name: "culture")
|
|
create(:proposal, tag_list: "culture", created_at: 8.days.ago)
|
|
|
|
expect(Proposal.for_summary.values.flatten).to be_empty
|
|
end
|
|
|
|
it "orders proposals by votes" do
|
|
create(:tag, :category, name: "culture")
|
|
create(:proposal, tag_list: "culture").update_column(:confidence_score, 2)
|
|
create(:proposal, tag_list: "culture").update_column(:confidence_score, 10)
|
|
create(:proposal, tag_list: "culture").update_column(:confidence_score, 5)
|
|
|
|
results = Proposal.for_summary.values.flatten
|
|
|
|
expect(results.map(&:confidence_score)).to eq [10, 5, 2]
|
|
end
|
|
|
|
it "orders groups alphabetically" do
|
|
create(:tag, :category, name: "health")
|
|
create(:tag, :category, name: "culture")
|
|
create(:tag, :category, name: "social services")
|
|
|
|
health_proposal = create(:proposal, tag_list: "health")
|
|
culture_proposal = create(:proposal, tag_list: "culture")
|
|
social_proposal = create(:proposal, tag_list: "social services")
|
|
|
|
results = Proposal.for_summary.values.flatten
|
|
|
|
expect(results).to eq [culture_proposal, health_proposal, social_proposal]
|
|
end
|
|
|
|
it "returns proposals grouped by tag" do
|
|
create(:tag, :category, name: "culture")
|
|
create(:tag, :category, name: "health")
|
|
|
|
proposal1 = create(:proposal, tag_list: "culture")
|
|
proposal2 = create(:proposal, tag_list: "culture")
|
|
proposal2.update_column(:confidence_score, 100)
|
|
proposal3 = create(:proposal, tag_list: "health")
|
|
|
|
proposal1.update_column(:confidence_score, 10)
|
|
proposal2.update_column(:confidence_score, 9)
|
|
|
|
expect(Proposal.for_summary).to include("culture" => [proposal1, proposal2], "health" => [proposal3])
|
|
end
|
|
end
|
|
|
|
describe "#to_param" do
|
|
it "returns a friendly url" do
|
|
expect(proposal.to_param).to eq "#{proposal.id} #{proposal.title}".parameterize
|
|
end
|
|
end
|
|
|
|
describe "retired" do
|
|
let!(:proposal1) { create(:proposal) }
|
|
let!(:proposal2) { create(:proposal, :retired) }
|
|
|
|
it "retired? is true" do
|
|
expect(proposal1.retired?).to eq false
|
|
expect(proposal2.retired?).to eq true
|
|
end
|
|
|
|
it "scope retired" do
|
|
expect(Proposal.retired).to eq [proposal2]
|
|
end
|
|
|
|
it "scope not_retired" do
|
|
expect(Proposal.not_retired).to eq [proposal1]
|
|
end
|
|
end
|
|
|
|
describe "archived" do
|
|
let!(:new_proposal) { create(:proposal) }
|
|
let!(:archived_proposal) { create(:proposal, :archived) }
|
|
|
|
it "archived? is true only for proposals created more than n (configured months) ago" do
|
|
expect(new_proposal.archived?).to eq false
|
|
expect(archived_proposal.archived?).to eq true
|
|
end
|
|
|
|
it "scope archived" do
|
|
expect(Proposal.archived).to eq [archived_proposal]
|
|
end
|
|
|
|
it "scope not archived" do
|
|
expect(Proposal.not_archived).to eq [new_proposal]
|
|
end
|
|
end
|
|
|
|
describe "selected" do
|
|
let!(:not_selected_proposal) { create(:proposal) }
|
|
let!(:selected_proposal) { create(:proposal, :selected) }
|
|
|
|
it "selected? is true" do
|
|
expect(not_selected_proposal.selected?).to be false
|
|
expect(selected_proposal.selected?).to be true
|
|
end
|
|
|
|
it "scope selected" do
|
|
expect(Proposal.selected).to eq [selected_proposal]
|
|
end
|
|
|
|
it "scope not_selected" do
|
|
expect(Proposal.not_selected).to eq [not_selected_proposal]
|
|
end
|
|
end
|
|
|
|
describe "public_for_api scope" do
|
|
it "returns proposals" do
|
|
proposal = create(:proposal)
|
|
|
|
expect(Proposal.public_for_api).to eq [proposal]
|
|
end
|
|
|
|
it "does not return hidden proposals" do
|
|
create(:proposal, :hidden)
|
|
|
|
expect(Proposal.public_for_api).to be_empty
|
|
end
|
|
end
|
|
|
|
describe "#user_to_notify" do
|
|
|
|
it "returns voters and followers" do
|
|
proposal = create(:proposal)
|
|
voter = create(:user, :level_two, votables: [proposal])
|
|
follower = create(:user, :level_two, followables: [proposal])
|
|
|
|
expect(proposal.users_to_notify).to eq([voter, follower])
|
|
end
|
|
|
|
it "returns voters and followers discarding duplicates" do
|
|
proposal = create(:proposal)
|
|
voter_and_follower = create(:user, :level_two, votables: [proposal], followables: [proposal])
|
|
|
|
expect(proposal.users_to_notify).to eq([voter_and_follower])
|
|
end
|
|
|
|
it "returns voters and followers except the proposal author" do
|
|
author = create(:user, :level_two)
|
|
voter_and_follower = create(:user, :level_two)
|
|
proposal = create(:proposal, author: author,
|
|
voters: [author, voter_and_follower],
|
|
followers: [author, voter_and_follower])
|
|
|
|
expect(proposal.users_to_notify).to eq([voter_and_follower])
|
|
end
|
|
|
|
end
|
|
|
|
describe "#recommendations" do
|
|
|
|
let(:user) { create(:user) }
|
|
|
|
it "does not return any proposals when user has not interests" do
|
|
create(:proposal)
|
|
|
|
expect(Proposal.recommendations(user)).to be_empty
|
|
end
|
|
|
|
it "returns proposals related to the user's interests ordered by cached_votes_up" do
|
|
create(:proposal, tag_list: "Sport", followers: [user])
|
|
|
|
proposal1 = create(:proposal, cached_votes_up: 1, tag_list: "Sport")
|
|
proposal2 = create(:proposal, cached_votes_up: 5, tag_list: "Sport")
|
|
proposal3 = create(:proposal, cached_votes_up: 10, tag_list: "Sport")
|
|
|
|
results = Proposal.recommendations(user).sort_by_recommendations
|
|
|
|
expect(results).to eq [proposal3, proposal2, proposal1]
|
|
end
|
|
|
|
it "does not return proposals unrelated to user interests" do
|
|
create(:proposal, tag_list: "Sport", followers: [user])
|
|
create(:proposal, tag_list: "Politics")
|
|
|
|
results = Proposal.recommendations(user)
|
|
|
|
expect(results).to be_empty
|
|
end
|
|
|
|
it "does not return proposals when user is follower" do
|
|
create(:proposal, tag_list: "Sport", followers: [user])
|
|
|
|
results = Proposal.recommendations(user)
|
|
|
|
expect(results).to be_empty
|
|
end
|
|
|
|
it "does not return proposals when user is the author" do
|
|
create(:proposal, tag_list: "Sport", followers: [user])
|
|
create(:proposal, author: user, tag_list: "Sport")
|
|
|
|
results = Proposal.recommendations(user)
|
|
|
|
expect(results).to be_empty
|
|
end
|
|
|
|
it "does not return archived proposals" do
|
|
create(:proposal, tag_list: "Sport", followers: [user])
|
|
create(:proposal, :archived, tag_list: "Sport")
|
|
|
|
results = Proposal.recommendations(user)
|
|
|
|
expect(results).to be_empty
|
|
end
|
|
|
|
it "does not return already supported proposals" do
|
|
create(:proposal, tag_list: "Health", followers: [user])
|
|
create(:proposal, tag_list: "Health", voters: [user])
|
|
|
|
results = Proposal.recommendations(user)
|
|
|
|
expect(results).to be_empty
|
|
end
|
|
|
|
end
|
|
|
|
describe "#send_new_actions_notification_on_create" do
|
|
|
|
before do
|
|
Setting["dashboard.emails"] = true
|
|
ActionMailer::Base.deliveries.clear
|
|
end
|
|
|
|
it "send notification after create when there are new actived actions" do
|
|
create(:dashboard_action, :proposed_action, :active, day_offset: 0, published_proposal: false)
|
|
create(:dashboard_action, :resource, :active, day_offset: 0, published_proposal: false)
|
|
|
|
create(:proposal, :draft)
|
|
|
|
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
|
end
|
|
|
|
it "Not send notification after create when there are not new actived actions" do
|
|
create(:dashboard_action, :proposed_action, :active, day_offset: 1, published_proposal: false)
|
|
create(:dashboard_action, :resource, :active, day_offset: 1, published_proposal: false)
|
|
|
|
create(:proposal, :draft)
|
|
|
|
expect(ActionMailer::Base.deliveries.count).to eq(0)
|
|
end
|
|
|
|
end
|
|
|
|
describe "#send_new_actions_notification_on_published" do
|
|
|
|
before do
|
|
Setting["dashboard.emails"] = true
|
|
ActionMailer::Base.deliveries.clear
|
|
end
|
|
|
|
it "send notification after published when there are new actived actions" do
|
|
create(:dashboard_action, :proposed_action, :active, day_offset: 0, published_proposal: true)
|
|
create(:dashboard_action, :resource, :active, day_offset: 0, published_proposal: true)
|
|
|
|
proposal = create(:proposal, :draft)
|
|
proposal.publish
|
|
|
|
expect(ActionMailer::Base.deliveries.count).to eq(1)
|
|
end
|
|
|
|
it "Not send notification after published when there are not new actived actions" do
|
|
create(:dashboard_action, :proposed_action, :active, day_offset: 1, published_proposal: true)
|
|
create(:dashboard_action, :resource, :active, day_offset: 1, published_proposal: true)
|
|
|
|
proposal = create(:proposal, :draft)
|
|
proposal.publish
|
|
|
|
expect(ActionMailer::Base.deliveries.count).to eq(0)
|
|
end
|
|
|
|
end
|
|
|
|
describe "milestone_tags" do
|
|
|
|
context "without milestone_tags" do
|
|
|
|
let(:proposal) { create(:proposal) }
|
|
|
|
it "do not have milestone_tags" do
|
|
expect(proposal.milestone_tag_list).to eq([])
|
|
expect(proposal.milestone_tags).to eq([])
|
|
end
|
|
|
|
it "add a new milestone_tag" do
|
|
proposal.milestone_tag_list = "tag1,tag2"
|
|
|
|
expect(proposal.milestone_tag_list).to eq(["tag1", "tag2"])
|
|
end
|
|
end
|
|
|
|
context "with milestone_tags" do
|
|
|
|
let(:proposal) { create(:proposal, :with_milestone_tags) }
|
|
|
|
it "has milestone_tags" do
|
|
expect(proposal.milestone_tag_list.count).to eq(1)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|