0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-07 13:19:19 +08:00
discourse/plugins/discourse-calendar/spec/requests/invitees_controller_spec.rb
Martin Brennan 57779cc4be
FIX: can_create_discourse_post_event? not checking user groups correctly (#42284)
This commit fixes `can_create_discourse_post_event?` manually checking
a user's groups, which doesn't account for pseudogroups like
`logged_in_users`. We should always use `user.in_any_groups?` for this.

In addition, the opportunity is taken here for some cleanup, moving
all the guardian extensions from plugin.rb into a proper
`GuardianExtensions` file for calendar, adding extra specs along the
way.

Finally, I've added a `Group.refresh_automatic_groups_for_user!`
method as a single-user variant of `Group.refresh_automatic_groups!`
that can use in fabricators for testing, to make this more
thorough/reliable
than manually adding group user records in `after_create` in the
fabricators
2026-08-04 16:09:50 +10:00

484 lines
16 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscoursePostEvent
describe InviteesController do
before do
Jobs.run_immediately!
SiteSetting.calendar_enabled = true
SiteSetting.discourse_post_event_enabled = true
sign_in(user)
end
let(:user) { Fabricate(:user, admin: true, refresh_auto_groups: true) }
let(:topic_1) { Fabricate(:topic, user: user) }
let(:post_1) { Fabricate(:post, user: user, topic: topic_1) }
describe "#index" do
context "for a post in a private category" do
let(:outside_user) { Fabricate(:user) }
let(:in_group_user) { Fabricate(:user) }
let(:group) { Fabricate(:group, users: [in_group_user]) }
let(:private_category) { Fabricate(:private_category, group:) }
let(:topic_1) { Fabricate(:topic, user: user, category: private_category) }
let(:post_1) { Fabricate(:post, user: user, topic: topic_1) }
let(:post_event_1) { Fabricate(:event, post: post_1) }
it "forbids non group user from seeing the list of invitees" do
sign_in(outside_user)
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json"
expect(response.status).to eq(403)
end
it "allows group user to see the list of invitees" do
sign_in(in_group_user)
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json"
expect(response.status).to eq(200)
end
end
context "for a private event in a public topic" do
fab!(:event_owner, :admin)
fab!(:public_topic) { Fabricate(:topic, user: event_owner) }
fab!(:public_post) { Fabricate(:post, user: event_owner, topic: public_topic) }
fab!(:viewer, :user)
fab!(:invitee_user, :user)
fab!(:restricted_group) do
Fabricate(
:group,
visibility_level: Group.visibility_levels[:owners],
members_visibility_level: Group.visibility_levels[:owners],
).tap { |group| group.add(invitee_user) }
end
fab!(:private_event) do
Fabricate(
:event,
post: public_post,
status: DiscoursePostEvent::Event.statuses[:private],
raw_invitees: [restricted_group.name],
)
end
before do
private_event.create_invitees(
[{ user_id: invitee_user.id, status: Invitee.statuses[:going] }],
)
end
it "does not disclose private event invitees to uninvited users" do
sign_in(viewer)
get "/discourse-post-event/events/#{private_event.id}/invitees.json"
expect(response.body).not_to include(invitee_user.username)
expect(response.status).to eq(403)
end
end
context "when params are included" do
let(:invitee1) { Fabricate(:user, username: "Francis", name: "Francis") }
let(:invitee2) { Fabricate(:user, username: "Francisco", name: "Francisco") }
let(:invitee3) { Fabricate(:user, username: "Frank", name: "Frank") }
let(:invitee4) { Fabricate(:user, username: "Franchesca", name: "Franchesca") }
let!(:random_user) { Fabricate(:user, username: "Franny") }
let(:post_event_1) do
pe = Fabricate(:event, post: post_1)
pe.create_invitees(
[
{ user_id: invitee1.id, status: Invitee.statuses[:going] },
{ user_id: invitee2.id, status: Invitee.statuses[:interested] },
{ user_id: invitee3.id, status: Invitee.statuses[:not_going] },
{ user_id: invitee4.id, status: Invitee.statuses[:going] },
],
)
pe
end
context "when user is allowed to act on post event" do
it "returns users extra suggested users when filtering the invitees by name" do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
filter: "Fran",
type: "going",
}
suggested = response.parsed_body[:meta][:suggested_users].map { |u| u[:username] }.sort
expect(suggested).to eq(%w[Francisco Frank Franny])
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
filter: "",
type: "going",
}
suggested = response.parsed_body.dig(:meta, :suggested_users)
expect(suggested).to be_blank
end
end
it "returns the correct amount of users when filtering the invitees by name" do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
filter: "Franc",
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(3)
end
it "returns the correct amount of users when filtering the invitees by type" do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
type: "interested",
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(1)
end
it "returns the correct amount of users when filtering the invitees by name and type" do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
filter: "Franc",
type: "going",
}
filteredInvitees = response.parsed_body["invitees"]
expect(filteredInvitees.count).to eq(2)
end
it "returns 400 when filtering by an invalid type" do
get "/discourse-post-event/events/#{post_event_1.id}/invitees.json",
params: {
type: "nonexistent",
}
expect(response.status).to eq(400)
end
end
end
describe "#create" do
let(:event) { Fabricate(:event, post: post_1) }
it "creates an invitee" do
post "/discourse-post-event/events/#{event.id}/invitees.json",
params: {
invitee: {
status: "going",
},
}
expect(response.status).to eq(200)
expect(Invitee).to exist(user_id: user.id, status: Invitee.statuses[:going])
end
it "allows staff to invite another user" do
other_user = Fabricate(:user)
post "/discourse-post-event/events/#{event.id}/invitees.json",
params: {
invitee: {
status: "interested",
user_id: other_user.id,
},
}
expect(response.status).to eq(200)
expect(Invitee).to exist(user_id: other_user.id, status: Invitee.statuses[:interested])
end
it "persists the recurring flag when joining" do
post "/discourse-post-event/events/#{event.id}/invitees.json",
params: {
invitee: {
status: "going",
recurring: true,
},
}
expect(response.status).to eq(200)
expect(Invitee).to exist(user_id: user.id, recurring: true)
end
it "returns 403 when non-staff tries to invite themselves to a private event" do
private_event = Fabricate(:event, post: post_1, status: "private")
other_user = Fabricate(:user)
sign_in(other_user)
expect do
post "/discourse-post-event/events/#{private_event.id}/invitees.json",
params: {
invitee: {
status: "going",
},
}
end.not_to change { Invitee.count }
expect(response.status).to eq(403)
end
context "when event is at max capacity" do
fab!(:post_2) { create_post(user: Fabricate(:admin), category: Fabricate(:category)) }
fab!(:user_a, :user)
fab!(:user_b, :user)
fab!(:full_event) do
pe = Fabricate(:event, post: post_2, max_attendees: 1)
pe.create_invitees([{ user_id: user_a.id, status: Invitee.statuses[:going] }])
pe
end
it "returns 422 when trying to join as going" do
sign_in(user_b)
post "/discourse-post-event/events/#{full_event.id}/invitees.json",
params: {
invitee: {
status: "going",
},
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"].join).to include("full")
end
it "allows interested when full" do
sign_in(user_b)
post "/discourse-post-event/events/#{full_event.id}/invitees.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(200)
end
end
end
describe "#update" do
let(:invitee_user) { Fabricate(:user) }
let(:event) do
pe = Fabricate(:event, post: post_1)
pe.create_invitees([{ user_id: invitee_user.id, status: Invitee.statuses[:going] }])
pe
end
let(:invitee) { event.invitees.first }
it "updates the invitee status" do
put "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(200)
expect(invitee.reload.status).to eq(Invitee.statuses[:interested])
end
it "persists the recurring flag when updating" do
put "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json",
params: {
invitee: {
status: "going",
recurring: true,
},
}
expect(response.status).to eq(200)
expect(invitee.reload.recurring).to eq(true)
end
it "returns 404 when invitee does not exist" do
put "/discourse-post-event/events/#{event.id}/invitees/999999.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(404)
end
it "returns 403 when user cannot act on invitee" do
lurker = Fabricate(:user)
sign_in(lurker)
put "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(403)
end
context "when event is at max capacity" do
fab!(:post_2) { create_post(user: Fabricate(:admin), category: Fabricate(:category)) }
fab!(:user_a, :user)
fab!(:user_b, :user)
fab!(:full_event) do
pe = Fabricate(:event, post: post_2, max_attendees: 1)
pe.create_invitees([{ user_id: user_a.id, status: Invitee.statuses[:going] }])
pe
end
fab!(:interested_invitee) do
Fabricate(
:invitee,
post_id: post_2.id,
user_id: user_b.id,
status: Invitee.statuses[:interested],
)
end
it "returns 422 when trying to change to going" do
sign_in(user_b)
put "/discourse-post-event/events/#{full_event.id}/invitees/#{interested_invitee.id}.json",
params: {
invitee: {
status: "going",
},
}
expect(response.status).to eq(422)
expect(response.parsed_body["errors"].join).to include("full")
end
it "allows changing to interested" do
sign_in(user_b)
put "/discourse-post-event/events/#{full_event.id}/invitees/#{interested_invitee.id}.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(200)
end
end
end
describe "#destroy" do
let(:invitee_user) { Fabricate(:user) }
let(:event) do
pe = Fabricate(:event, post: post_1)
pe.create_invitees([{ user_id: invitee_user.id, status: Invitee.statuses[:going] }])
pe
end
let(:invitee) { event.invitees.first }
it "destroys the invitee as staff" do
delete "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json"
expect(response.status).to eq(200)
expect(Invitee.exists?(invitee.id)).to eq(false)
end
it "destroys the invitee as the invitee owner" do
sign_in(invitee_user)
delete "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json"
expect(response.status).to eq(200)
expect(Invitee.exists?(invitee.id)).to eq(false)
end
it "returns 403 when user cannot act on invitee" do
lurker = Fabricate(:user)
sign_in(lurker)
delete "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json"
expect(response.status).to eq(403)
expect(Invitee.exists?(invitee.id)).to eq(true)
end
it "returns 404 when invitee does not exist" do
delete "/discourse-post-event/events/#{event.id}/invitees/999999.json"
expect(response.status).to eq(404)
end
end
end
describe "anonymous access to InviteesController" do
before do
SiteSetting.calendar_enabled = true
SiteSetting.discourse_post_event_enabled = true
end
fab!(:admin_user) { Fabricate(:user, admin: true, refresh_auto_groups: true) }
fab!(:topic) { Fabricate(:topic, user: admin_user) }
fab!(:post_1) { Fabricate(:post, user: admin_user, topic: topic) }
fab!(:event) { Fabricate(:event, post: post_1) }
fab!(:invitee_user, :user)
fab!(:invitee) do
DiscoursePostEvent::Invitee.create!(
post_id: post_1.id,
user_id: invitee_user.id,
status: DiscoursePostEvent::Invitee.statuses[:going],
)
end
it "requires login for create" do
post "/discourse-post-event/events/#{event.id}/invitees.json",
params: {
invitee: {
status: "going",
},
}
expect(response.status).to eq(403)
end
it "requires login for update" do
put "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json",
params: {
invitee: {
status: "interested",
},
}
expect(response.status).to eq(403)
end
it "requires login for destroy" do
delete "/discourse-post-event/events/#{event.id}/invitees/#{invitee.id}.json"
expect(response.status).to eq(403)
end
context "for a private event in a public topic" do
fab!(:private_event_post) { Fabricate(:post, user: admin_user, topic: topic) }
fab!(:restricted_group) do
Fabricate(
:group,
visibility_level: Group.visibility_levels[:owners],
members_visibility_level: Group.visibility_levels[:owners],
).tap { |group| group.add(invitee_user) }
end
fab!(:private_event) do
Fabricate(
:event,
post: private_event_post,
status: DiscoursePostEvent::Event.statuses[:private],
raw_invitees: [restricted_group.name],
)
end
before do
private_event.create_invitees(
[{ user_id: invitee_user.id, status: DiscoursePostEvent::Invitee.statuses[:going] }],
)
end
it "does not disclose private event invitees to anonymous viewers" do
get "/discourse-post-event/events/#{private_event.id}/invitees.json"
expect(response.body).not_to include(invitee_user.username)
expect(response.status).to eq(403)
end
end
end
end