diff --git a/spec/factories.rb b/spec/factories.rb index 73512a689..106502fc6 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -943,4 +943,11 @@ LOREM_IPSUM factory :related_content do end + factory :newsletter do + sequence(:subject) { |n| "Subject #{n}" } + segment_recipient [1, 2, 3, 4, 5, 6].sample + sequence(:from) { |n| "noreply#{n}@consul.dev" } + sequence(:body) { |n| "Body #{n}" } + end + end diff --git a/spec/features/admin/newsletter/emails_spec.rb b/spec/features/admin/newsletter/emails_spec.rb new file mode 100644 index 000000000..a73f4efc8 --- /dev/null +++ b/spec/features/admin/newsletter/emails_spec.rb @@ -0,0 +1,129 @@ +require 'rails_helper' + +feature "Admin newsletter emails" do + + background do + admin = create(:administrator) + login_as(admin.user) + create(:budget) + end + + scenario "Show" do + newsletter = create(:newsletter, subject: "This is a subject", + segment_recipient: 1, + from: "no-reply@consul.dev", + body: "This is a body") + + visit admin_newsletter_path(newsletter) + + expect(page).to have_content "This is a subject" + expect(page).to have_content I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}") + expect(page).to have_content "no-reply@consul.dev" + expect(page).to have_content "This is a body" + end + + scenario "Index" do + 3.times { create(:newsletter) } + + visit admin_newsletters_path + + expect(page).to have_css(".newsletter", count: 3) + + Newsletter.all.each do |newsletter| + within("#newsletter_#{newsletter.id}") do + expect(page).to have_content newsletter.subject + expect(page).to have_content I18n.t("admin.segment_recipient.#{newsletter.segment_recipient}") + end + end + end + + scenario "Create" do + visit admin_newsletters_path + click_link "New newsletter" + + fill_in_newsletter_form(subject: "This is a subject", + segment_recipient: "Proposal authors", + body: "This is a body" ) + click_button "Create Newsletter" + + expect(page).to have_content "Newsletter created successfully" + expect(page).to have_content "This is a subject" + expect(page).to have_content "Proposal authors" + expect(page).to have_content "no-reply@consul.dev" + expect(page).to have_content "This is a body" + end + + scenario "Update" do + newsletter = create(:newsletter) + + visit admin_newsletters_path + within("#newsletter_#{newsletter.id}") do + click_link "Edit" + end + + fill_in_newsletter_form(subject: "This is a subject", + segment_recipient: "Investment authors in the current budget", + body: "This is a body" ) + click_button "Update Newsletter" + + expect(page).to have_content "Newsletter updated successfully" + expect(page).to have_content "This is a subject" + expect(page).to have_content "Investment authors in the current budget" + expect(page).to have_content "no-reply@consul.dev" + expect(page).to have_content "This is a body" + end + + scenario "Destroy" do + newsletter = create(:newsletter) + + visit admin_newsletters_path + within("#newsletter_#{newsletter.id}") do + click_link "Delete" + end + + expect(page).to have_content "Newsletter deleted successfully" + expect(page).to have_css(".newsletter", count: 0) + end + + scenario 'Errors on create' do + visit new_admin_newsletter_path + + click_button "Create Newsletter" + + expect(page).to have_content error_message + end + + scenario "Errors on update" do + newsletter = create(:newsletter) + visit edit_admin_newsletter_path(newsletter) + + fill_in "newsletter_subject", with: "" + click_button "Update Newsletter" + + expect(page).to have_content error_message + end + + scenario "Send newsletter email", :js do + newsletter = create(:newsletter) + visit admin_newsletter_path(newsletter) + + click_link "Send" + + total_users = newsletter.list_of_recipients.count + page.accept_confirm("Are you sure you want to send this newsletter to #{total_users} users?") + + expect(page).to have_content "Newsletter sent successfully" + end + + scenario "Select list of users to send newsletter" do + Newsletter.segment_recipients.each_key do |user_group| + visit new_admin_newsletter_path + + fill_in_newsletter_form + select I18n.t("admin.segment_recipient.#{user_group}"), from: 'newsletter_segment_recipient' + click_button "Create Newsletter" + + expect(page).to have_content(I18n.t("admin.segment_recipient.#{user_group}")) + end + end +end diff --git a/spec/features/admin/newsletters_spec.rb b/spec/features/admin/newsletters_spec.rb deleted file mode 100644 index fccb63dc8..000000000 --- a/spec/features/admin/newsletters_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'rails_helper' - -feature 'Admin newsletters emails' do - - let(:download_button_text) { 'Download zip with users list' } - - background do - @admin = create(:administrator) - @newsletter_user = create(:user, newsletter: true) - @non_newsletter_user = create(:user, newsletter: false) - login_as(@admin.user) - visit admin_newsletters_path - end - - scenario 'Index' do - expect(page).to have_content download_button_text - end - - scenario 'Download newsletter email zip' do - click_link download_button_text - downloaded_file_content = Zip::InputStream.open(StringIO.new(page.body)).get_next_entry.get_input_stream {|is| is.read } - expect(downloaded_file_content).to include(@admin.user.email, @newsletter_user.email) - expect(downloaded_file_content).not_to include(@non_newsletter_user.email) - end -end - diff --git a/spec/features/emails_spec.rb b/spec/features/emails_spec.rb index 02892ef18..aeacbbe8f 100644 --- a/spec/features/emails_spec.rb +++ b/spec/features/emails_spec.rb @@ -491,7 +491,34 @@ feature 'Emails' do end + context "Newsletter" do + + scenario "Send newsletter email to selected users" do + admin = create(:administrator) + login_as(admin.user) + + visit new_admin_newsletter_path + fill_in_newsletter_form + click_button "Create Newsletter" + + expect(page).to have_content "Newsletter created successfully" + + click_link "Send" + + UserSegments.send(Newsletter.first.segment_recipient).each do |user| + expect(unread_emails_for(user.email).count).to eq 1 + end + + email = open_last_email + expect(email).to have_subject('This is a different subject') + expect(email).to deliver_from('no-reply@consul.dev') + expect(email.body.encoded).to include('This is a different body') + end + + end + context "Users without email" do + scenario "should not receive emails", :js do user = create(:user, :verified, email_on_comment: true) proposal = create(:proposal, author: user) @@ -500,5 +527,6 @@ feature 'Emails' do expect { open_last_email }.to raise_error "No email has been sent!" end + end end diff --git a/spec/lib/user_segments_spec.rb b/spec/lib/user_segments_spec.rb new file mode 100644 index 000000000..77dff6a27 --- /dev/null +++ b/spec/lib/user_segments_spec.rb @@ -0,0 +1,153 @@ +require 'rails_helper' + +describe UserSegments do + let(:user1) { create(:user) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + + describe "#all_users" do + it "returns all active users with newsletter enabled" do + active_user1 = create(:user, newsletter: true) + active_user2 = create(:user, newsletter: true) + active_user3 = create(:user, newsletter: false) + erased_user = create(:user, erased_at: Time.current) + + expect(described_class.all_users).to include active_user1 + expect(described_class.all_users).to include active_user2 + expect(described_class.all_users).not_to include active_user3 + expect(described_class.all_users).not_to include erased_user + end + end + + describe "#proposal_authors" do + it "returns users that have created a proposal" do + proposal = create(:proposal, author: user1) + + proposal_authors = described_class.proposal_authors + expect(proposal_authors).to include user1 + expect(proposal_authors).not_to include user2 + end + + it "does not return duplicated users" do + proposal1 = create(:proposal, author: user1) + proposal2 = create(:proposal, author: user1) + + proposal_authors = described_class.proposal_authors + expect(proposal_authors).to contain_exactly(user1) + end + end + + describe "#investment_authors" do + it "returns users that have created a budget investment" do + investment = create(:budget_investment, author: user1) + budget = create(:budget) + investment.update(budget: budget) + + investment_authors = described_class.investment_authors + expect(investment_authors).to include user1 + expect(investment_authors).not_to include user2 + end + + it "does not return duplicated users" do + investment1 = create(:budget_investment, author: user1) + investment2 = create(:budget_investment, author: user1) + budget = create(:budget) + investment1.update(budget: budget) + investment2.update(budget: budget) + + investment_authors = described_class.investment_authors + expect(investment_authors).to contain_exactly(user1) + end + end + + describe "#feasible_and_undecided_investment_authors" do + it "returns authors of a feasible or an undecided budget investment" do + feasible_investment = create(:budget_investment, :feasible, author: user1) + undecided_investment = create(:budget_investment, :undecided, author: user2) + unfeasible_investment = create(:budget_investment, :unfeasible, author: user3) + budget = create(:budget) + feasible_investment.update(budget: budget) + undecided_investment.update(budget: budget) + unfeasible_investment.update(budget: budget) + + investment_authors = described_class.feasible_and_undecided_investment_authors + expect(investment_authors).to include user1 + expect(investment_authors).to include user2 + expect(investment_authors).not_to include user3 + end + + it "does not return duplicated users" do + feasible_investment = create(:budget_investment, :feasible, author: user1) + undecided_investment = create(:budget_investment, :undecided, author: user1) + budget = create(:budget) + feasible_investment.update(budget: budget) + undecided_investment.update(budget: budget) + + investment_authors = described_class.feasible_and_undecided_investment_authors + expect(investment_authors).to contain_exactly(user1) + end + end + + describe "#selected_investment_authors" do + it "returns authors of selected budget investments" do + selected_investment = create(:budget_investment, :selected, author: user1) + unselected_investment = create(:budget_investment, :unselected, author: user2) + budget = create(:budget) + selected_investment.update(budget: budget) + unselected_investment.update(budget: budget) + + investment_authors = described_class.selected_investment_authors + expect(investment_authors).to include user1 + expect(investment_authors).not_to include user2 + end + + it "does not return duplicated users" do + selected_investment1 = create(:budget_investment, :selected, author: user1) + selected_investment2 = create(:budget_investment, :selected, author: user1) + budget = create(:budget) + selected_investment1.update(budget: budget) + selected_investment2.update(budget: budget) + + investment_authors = described_class.selected_investment_authors + expect(investment_authors).to contain_exactly(user1) + end + end + + describe "#winner_investment_authors" do + it "returns authors of winner budget investments" do + winner_investment = create(:budget_investment, :winner, author: user1) + selected_investment = create(:budget_investment, :selected, author: user2) + budget = create(:budget) + winner_investment.update(budget: budget) + selected_investment.update(budget: budget) + + investment_authors = described_class.winner_investment_authors + expect(investment_authors).to include user1 + expect(investment_authors).not_to include user2 + end + + it "does not return duplicated users" do + winner_investment1 = create(:budget_investment, :winner, author: user1) + winner_investment2 = create(:budget_investment, :winner, author: user1) + budget = create(:budget) + winner_investment1.update(budget: budget) + winner_investment2.update(budget: budget) + + investment_authors = described_class.winner_investment_authors + expect(investment_authors).to contain_exactly(user1) + end + end + + describe "#current_budget_investments" do + it "only returns investments from the current budget" do + investment1 = create(:budget_investment, author: create(:user)) + investment2 = create(:budget_investment, author: create(:user)) + budget = create(:budget) + investment1.update(budget: budget) + + current_budget_investments = described_class.current_budget_investments + expect(current_budget_investments).to include investment1 + expect(current_budget_investments).not_to include investment2 + end + end +end diff --git a/spec/models/newsletter_spec.rb b/spec/models/newsletter_spec.rb new file mode 100644 index 000000000..09d66055d --- /dev/null +++ b/spec/models/newsletter_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +describe Newsletter do + let(:newsletter) { build(:newsletter) } + + it "is valid" do + expect(newsletter).to be_valid + end + + it 'is not valid without a subject' do + newsletter.subject = nil + expect(newsletter).not_to be_valid + end + + it 'is not valid without a segment_recipient' do + newsletter.segment_recipient = nil + expect(newsletter).not_to be_valid + end + + it 'is not valid without a from' do + newsletter.from = nil + expect(newsletter).not_to be_valid + end + + it 'is not valid without a body' do + newsletter.body = nil + expect(newsletter).not_to be_valid + end + + it 'validates from attribute email format' do + newsletter.from = "this_is_not_an_email" + expect(newsletter).not_to be_valid + end +end diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index c199b0124..af7b447a3 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -357,4 +357,11 @@ module CommonActions end end + def fill_in_newsletter_form(options = {}) + fill_in "newsletter_subject", with: (options[:subject] || "This is a different subject") + select (options[:segment_recipient] || 'All users'), from: 'newsletter_segment_recipient' + fill_in "newsletter_from", with: (options[:from] || "no-reply@consul.dev") + fill_in "newsletter_body", with: (options[:body] || "This is a different body") + end + end