docbrown/spec/requests/dashboard_spec.rb
Jeremy Friesen 4509e81dd5
Ensuring the same policies for analytics (#16997)
Prior to this commit the following situation existed:

> The path /dashboard/analytics/org/:id requires user
> authentication (e.g. signed in). However, it does not enforce
> authorization. Anyone can see this page. The page, however, uses
> javascript to populate the data. So no information, aside from the org
> name associated with the :id leaks out. The javascript API end point
> enforces organization membership.
>
> I would expect that the authorization in the HTML rendering would be
> the same as the javascript API end point.

This commit ensures that the dashboards#analytics end point uses the
same policy logic as the API analytics end points.  Further, it keeps
folks who aren't org members out of the base HTML page for other orgs.

Closes forem/forem/#16985
2022-03-25 14:57:01 -04:00

410 lines
14 KiB
Ruby

require "rails_helper"
RSpec.describe "Dashboards", type: :request do
let(:user) { create(:user) }
let(:second_user) { create(:user) }
let(:super_admin) { create(:user, :super_admin) }
let(:article) { create(:article, user: user) }
let(:unpublished_article) { create(:article, user: user, published: false) }
let(:organization) { create(:organization) }
describe "GET /dashboard" do
context "when not logged in" do
it "redirects to /enter" do
get "/dashboard"
expect(response).to redirect_to("/enter")
end
end
context "when logged in" do
before do
sign_in user
article
end
it "renders user's articles" do
get "/dashboard"
expect(response.body).to include(CGI.escapeHTML(article.title))
end
it 'shows "STATS" for articles' do
article = create(:article, user: user)
get "/dashboard"
expect(response.body).to include("Stats")
expect(response.body).to include("#{article.path}/stats")
end
it "renders the delete button for drafts" do
unpublished_article
get "/dashboard"
expect(response.body).to include "Delete"
end
it "renders subscriptions for articles with subscriptions" do
allow(user).to receive(:has_role?).and_call_original
allow(user).to receive(:has_role?).with(:restricted_liquid_tag,
LiquidTags::UserSubscriptionTag).and_return(true)
article_with_user_subscription_tag = create(:article, user: user, with_user_subscription_tag: true)
create(:user_subscription,
subscriber_id: second_user.id,
subscriber_email: second_user.email,
author_id: article_with_user_subscription_tag.user_id,
user_subscription_sourceable: article_with_user_subscription_tag)
get "/dashboard"
expect(response.body).to include "Subscriptions"
end
it "renders pagination if minimum amount of posts" do
create_list(:article, 52, user: user)
get "/dashboard"
expect(response.body).to include "pagination"
end
it "does not render pagination if less than one full page" do
create_list(:article, 3, user: user)
get "/dashboard"
expect(response.body).not_to include "pagination"
end
it "renders a link to analytics dashboard" do
get dashboard_path
expect(response.body).to include("Analytics")
end
it "renders a link to analytics for the org" do
create(:organization_membership, type_of_user: :admin, organization: organization, user: user)
get dashboard_path
expect(response.body).to include(CGI.escapeHTML("Analytics for #{organization.name}"))
end
it "does not render a link to upload a video when enable_video_upload is false" do
get dashboard_path
allow(Settings::General).to receive(:enable_video_upload).and_return(false)
expect(response.body).not_to include("Upload a video")
end
it "does not render a link to upload a video for a recent user" do
get dashboard_path
allow(Settings::General).to receive(:enable_video_upload).and_return(true)
expect(response.body).not_to include("Upload a video")
end
end
context "when logged but has no articles nor can create them" do
it "redirects to /dashboard/following_tags" do
sign_in user
# [@jeremyf] I'm choosing not to setup the exact conditions of the data for this to be true.
# Instead, I'm relying on that function to already be tested.
#
# rubocop:disable RSpec/AnyInstance
# Pundit does not make it easy to stub the policy().method questions so I'm using the any instance antics.
allow_any_instance_of(ArticlePolicy)
.to receive(:has_existing_articles_or_can_create_new_ones?)
.and_return(false)
# rubocop:enable RSpec/AnyInstance
get dashboard_path
expect(response).to redirect_to("/dashboard/following_tags")
end
end
context "when logged in as a super admin" do
it "renders the specified user's articles" do
article
user
sign_in super_admin
get "/dashboard/#{user.username}"
expect(response.body).to include(CGI.escapeHTML(article.title))
end
end
context "when logged in as a non recent user with enable_video_upload set to true on the Forem" do
it "renders a link to upload a video" do
Timecop.freeze(Time.current) do
user.update!(created_at: 3.weeks.ago)
allow(Settings::General).to receive(:enable_video_upload).and_return(true)
sign_in user
get dashboard_path
expect(response.body).to include("Upload a video")
end
end
end
end
describe "GET /dashboard/organization" do
let(:organization) { create(:organization) }
context "when not logged in" do
it "redirects to /enter" do
get "/dashboard/organization"
expect(response).to redirect_to("/enter")
end
end
context "when logged in" do
it "renders user's organization articles" do
create(:organization_membership, user: user, organization: organization, type_of_user: "admin")
article.update(organization_id: organization.id)
sign_in user
get "/dashboard/organization/#{organization.id}"
expect(response.body).to include "crayons-logo"
end
it "does not render the delete button for other org member's drafts" do
create(:organization_membership, user: user, organization: organization, type_of_user: "member")
create(:organization_membership, user: second_user, organization: organization, type_of_user: "admin")
unpublished_article.update(organization_id: organization.id)
sign_in second_user
get "/dashboard/organization/#{organization.id}"
expect(response.body).not_to include("Delete")
expect(response.body).to include(ERB::Util.html_escape(unpublished_article.title))
end
end
context "when logged in but not member of org" do
it "renders unauthorized" do
sign_in user
expect do
get "/dashboard/organization/#{organization.id}"
end.to raise_error(Pundit::NotAuthorizedError)
end
end
end
describe "GET /dashboard/following" do
context "when not logged in" do
it "redirects to /enter" do
get "/dashboard/following"
expect(response).to redirect_to("/enter")
end
end
describe "followed users section" do
before do
sign_in user
user.follow second_user
user.reload
get "/dashboard/following_users"
end
it "renders followed users count" do
expect(response.body).to include "Following users (1)"
end
it "lists followed users" do
expect(response.body).to include CGI.escapeHTML(second_user.name)
end
end
describe "followed tags section" do
let(:tag) { create(:tag) }
before do
sign_in user
user.follow tag
user.reload
get "/dashboard/following_tags"
end
it "renders followed tags count" do
expect(response.body).to include "Following tags (1)"
end
it "lists followed tags" do
expect(response.body).to include tag.name
end
end
describe "followed organizations section" do
let(:organization) { create(:organization) }
before do
sign_in user
user.follow organization
user.reload
get "/dashboard/following_organizations"
end
it "renders followed organizations count" do
expect(response.body).to include "Following organizations (1)"
end
it "lists followed organizations" do
expect(response.body).to include CGI.escapeHTML(organization.name)
end
end
describe "followed podcasts section" do
let(:podcast) { create(:podcast) }
before do
sign_in user
user.follow podcast
user.reload
get "/dashboard/following_podcasts"
end
it "renders followed podcast count" do
expect(response.body).to include "Following podcasts (1)"
end
it "lists followed podcasts" do
expect(response.body).to include podcast.name
end
end
end
describe "GET /dashboard/user_followers" do
context "when not logged in" do
it "redirects to /enter" do
get "/dashboard/user_followers"
expect(response).to redirect_to("/enter")
end
end
context "when logged in" do
it "renders the current user's followers" do
second_user.follow user
sign_in user
get "/dashboard/user_followers"
expect(response.body).to include CGI.escapeHTML(second_user.name)
end
end
end
describe "GET /dashboard/analytics" do
context "when not logged in" do
it "raises unauthorized" do
get "/dashboard/analytics"
expect(response).to redirect_to("/enter")
end
end
context "when user is signed in" do
it "shows page properly" do
sign_in user
get "/dashboard/analytics"
expect(response.body).to include("Analytics")
end
it "page always contain back to dashboard button" do
sign_in user
get "/dashboard/analytics"
within "nav" do
expect(page).to have_selector("a[href='/dashboard']")
end
end
end
context "when user is an org admin" do
it "shows page properly" do
org = create :organization
create(:organization_membership, user: user, organization: org, type_of_user: "admin")
sign_in user
get "/dashboard/analytics/org/#{org.id}"
expect(response.body).to include("Analytics")
end
end
context "when user is an org member" do
it "shows page properly" do
org = create :organization
create(:organization_membership, user: user, organization: org)
sign_in user
get "/dashboard/analytics/org/#{org.id}"
expect(response.body).to include("Analytics")
end
end
end
describe "GET /dashboard/subscriptions" do
let(:author) { create(:user) }
let(:article_with_user_subscription_tag) { create(:article, user: author, with_user_subscription_tag: true) }
let(:params) do
{ source_type: article_with_user_subscription_tag.class.name, source_id: article_with_user_subscription_tag.id }
end
before do
# Stub roles because adding them normally can cause flaky specs
allow(author).to receive(:has_role?).and_call_original
allow(author).to receive(:has_role?).with(:restricted_liquid_tag,
LiquidTags::UserSubscriptionTag).and_return(true)
sign_in author
end
it "renders subscriptions" do
user_subscription = create(:user_subscription,
subscriber_id: second_user.id,
subscriber_email: second_user.email,
author_id: article_with_user_subscription_tag.user_id,
user_subscription_sourceable: article_with_user_subscription_tag)
get "/dashboard/subscriptions", params: params
expect(response.body).to include(user_subscription.subscriber_email)
end
it "displays a message if no subscriptions are found" do
get "/dashboard/subscriptions", params: params
expect(response.body).to include(CGI.escapeHTML("You don't have any subscribers for this"))
end
it "raises unauthorized when trying to access a source the user doesn't own" do
unauthorized_article = create(:article, :with_user_subscription_tag_role_user, with_user_subscription_tag: true)
create(:user_subscription,
subscriber_id: second_user.id,
subscriber_email: second_user.email,
author_id: unauthorized_article.user_id,
user_subscription_sourceable: unauthorized_article)
unauthorized_article_params = { source_type: unauthorized_article.class.name, source_id: unauthorized_article.id }
expect do
get "/dashboard/subscriptions", params: unauthorized_article_params
end.to raise_error(Pundit::NotAuthorizedError)
end
it "raises an error for disallowed source_types" do
invalid_source_type_params = { source_type: "Comment", source_id: 1 }
expect do
get "/dashboard/subscriptions", params: invalid_source_type_params
end.to raise_error(ActiveRecord::RecordNotFound)
end
it "raises an error when the source can't be found" do
nonexistent_article_params = { source_type: article.class.name, source_id: article.id + 999 }
expect do
get "/dashboard/subscriptions", params: nonexistent_article_params
end.to raise_error(ActiveRecord::RecordNotFound)
end
it "renders pagination if minimum amount of subscriptions" do
create_list(:user_subscription,
102, # Current pagination limit is 100
author: author,
user_subscription_sourceable: article_with_user_subscription_tag)
get "/dashboard/subscriptions", params: params
expect(response.body).to include "pagination"
end
it "does not render pagination if less than one full page" do
create_list(:user_subscription,
5,
author: author,
user_subscription_sourceable: article_with_user_subscription_tag)
get "/dashboard/subscriptions", params: params
expect(response.body).not_to include "pagination"
end
end
end