discourse/plugins/discourse-assign/spec/requests/list_controller_spec.rb
Régis HANOL 026eae7352
FIX: show group Assignments tab based on assignable_level (#39085)
The Assignments tab on group pages had two problematic visibility
conditions that caused it to disappear unexpectedly:

1. `can_show_assigned_tab?` checked whether ALL members of the group
belonged to an `assign_allowed_on_groups` group. This meant a single
member outside those groups would hide the tab for everyone — even when
the group was fully assignable and had active assignments.

2. The frontend required `assignment_count > 0`, so the tab silently
vanished when the last assignment was resolved, with no indication of
whether the feature was unconfigured or simply empty.

The root cause is that tab visibility was answering the wrong question:
"can all members use the assign feature?" instead of "is this group set
up to receive assignments?"

This replaces the member-overlap SQL in `can_show_assigned_tab?` with a
simple check on `assignable_level > nobody` — the setting that actually
controls whether a group can receive assignments. The `assignment_count
> 0` gate is removed from the frontend so the tab stays visible with an
empty state, consistent with other group tabs.

Also fixes the `assign_allowed_on_groups` setting description which
incorrectly stated it controls who can be assigned topics (that's
`assignable_level`).

Ref - t/179679
2026-04-08 16:50:27 +02:00

568 lines
18 KiB
Ruby

# frozen_string_literal: true
require_relative "../support/assign_allowed_group"
describe ListController do
before do
SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:trust_level_0]
SiteSetting.assign_enabled = true
end
fab!(:user, :active_user)
fab!(:user2, :user)
let(:admin) { Fabricate(:admin) }
let(:post) { Fabricate(:post) }
describe "only allow users from allowed groups" do
include_context "with group that is allowed to assign"
it "filters requests where current_user is not member of an allowed group" do
sign_in(user)
SiteSetting.assign_allowed_on_groups = ""
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
expect(response.status).to eq(403)
get "/topics/messages-assigned/#{user.username_lower}.json"
expect(response.status).to eq(403)
end
it "as an anon user" do
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
expect(response.status).to eq(403)
get "/topics/messages-assigned/#{user.username_lower}.json"
expect(response.status).to eq(403)
end
it "as an admin user" do
sign_in(admin)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
expect(response.status).to eq(200)
get "/topics/messages-assigned/#{user.username_lower}.json"
expect(response.status).to eq(200)
end
end
describe "#group_topics_assigned" do
include_context "with group that is allowed to assign"
fab!(:post1, :post)
fab!(:post2, :post)
fab!(:post3, :post)
fab!(:topic) { post3.topic }
fab!(:topic1) { post1.topic }
fab!(:topic2) { post2.topic }
before do
add_to_assign_allowed_group(user)
Assigner.new(topic1, user).assign(user)
Assigner.new(topic2, user).assign(user2)
sign_in(user)
end
it "returns user-assigned-topics-list of users in the assigned_allowed_group and doesn't include deleted topic" do
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
expect(
JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["assigned_to_user"]["id"] },
).to match_array([user.id])
end
it "returns user-assigned-topics-list of users in the assigned_allowed_group and doesn't include inactive topics" do
Assignment.where(assigned_to: user, target: topic1).update_all(active: false)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
expect(response.parsed_body["topic_list"]["topics"]).to be_empty
end
it "returns empty user-assigned-topics-list for users not in the assigned_allowed_group" do
ids = []
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
JSON.parse(response.body)["topic_list"]["topics"].each do |t|
ids.push(t["assigned_to_user"]["id"]) if t["assigned_to_user"]["id"] == user2.id
end
expect(ids).to be_empty
end
it "returns the correct more_topics_url with a unicode group name" do
SiteSetting.unicode_usernames = true
unicode_group =
Fabricate(:group, name: "管理员", assignable_level: Group::ALIAS_LEVELS[:everyone])
unicode_group.add(user)
SiteSetting.assign_allowed_on_groups += "|#{unicode_group.id}"
stub_const(TopicQuery, "DEFAULT_PER_PAGE_COUNT", 1) do
get "/topics/group-topics-assigned/#{UrlHelper.encode_component(unicode_group.name)}.json"
expect(response.status).to eq(200)
expect(response.parsed_body["topic_list"]["more_topics_url"]).to eq(
"/topics/group-topics-assigned/%E7%AE%A1%E7%90%86%E5%91%98?page=1",
)
end
end
it "doesn't return deleted topics" do
sign_in(admin)
Assigner.new(topic, user).assign(user)
delete "/t/#{topic.id}.json"
topic.reload
id = 0
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json"
JSON.parse(response.body)["topic_list"]["topics"].each do |t|
id = t.id if t["id"] == topic.id
end
expect(id).to eq(0)
end
end
describe "#sorting messages_assigned and group_topics_assigned" do
include_context "with group that is allowed to assign"
fab!(:post1, :post)
fab!(:post2, :post)
fab!(:post3, :post)
fab!(:topic1) { post1.topic }
fab!(:topic2) { post2.topic }
fab!(:topic3) { post3.topic }
before do
add_to_assign_allowed_group(user)
add_to_assign_allowed_group(user2)
Assigner.new(post1.topic, user).assign(user)
Assigner.new(post2.topic, user).assign(user2)
Assigner.new(post3.topic, user).assign(user)
sign_in(user)
end
it "group_topics_assigned returns sorted topicsList" do
topic1.bumped_at = Time.now
topic2.bumped_at = 1.day.ago
topic3.bumped_at = 3.days.ago
topic1.views = 3
topic2.views = 5
topic3.views = 1
topic1.posts_count = 3
topic2.posts_count = 1
topic3.posts_count = 5
topic1.save!
topic2.save!
topic3.save!
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=posts"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic2.id, topic1.id, topic3.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=views"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic1.id, topic2.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=activity"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic2.id, topic1.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=posts&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic1.id, topic2.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=views&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic2.id, topic1.id, topic3.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json?order=activity&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic2.id, topic3.id],
)
end
it "messages_assigned returns sorted topicsList" do
topic1.bumped_at = Time.now
topic3.bumped_at = 3.days.ago
topic1.views = 3
topic3.views = 1
topic1.posts_count = 3
topic3.posts_count = 5
topic1.reload
topic3.reload
get "/topics/messages-assigned/#{user.username}.json?order=posts"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic3.id],
)
get "/topics/messages-assigned/#{user.username}.json?order=views"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic1.id],
)
get "/topics/messages-assigned/#{user.username}.json?order=activity"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic1.id],
)
get "/topics/messages-assigned/#{user.username}.json?order=posts&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic3.id, topic1.id],
)
get "/topics/messages-assigned/#{user.username}.json?order=views&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic3.id],
)
get "/topics/messages-assigned/#{user.username}.json?order=activity&ascending=true"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic3.id],
)
end
end
describe "filtering of topics as per parameter" do
include_context "with group that is allowed to assign"
fab!(:post1, :post)
fab!(:post2, :post)
fab!(:post3, :post)
fab!(:topic1) { post1.topic }
fab!(:topic2) { post2.topic }
fab!(:topic3) { post3.topic }
before do
SearchIndexer.enable
add_to_assign_allowed_group(user)
add_to_assign_allowed_group(user2)
Assigner.new(post1.topic, user).assign(user)
Assigner.new(post2.topic, user).assign(user2)
Assigner.new(post3.topic, user).assign(user)
sign_in(user)
end
after { SearchIndexer.disable }
it "returns topics as per filter for #group_topics_assigned" do
topic1.title = "QUnit testing is love"
topic2.title = "RSpec testing is too fun"
topic3.title = "Testing is main part of programming"
topic1.save!
topic2.save!
topic3.save!
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json",
params: {
search: "Testing",
}
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic2.id, topic3.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json",
params: {
search: "RSpec",
}
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic2.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json",
params: {
search: "love",
}
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id],
)
end
it "returns topics as per filter for #group_topics_assigned" do
topic1.title = "QUnit testing is love"
topic2.title = "RSpec testing is too fun"
topic3.title = "Testing is main part of programming"
topic1.save!
topic2.save!
topic3.save!
get "/topics/messages-assigned/#{user.username}.json", params: { search: "Testing" }
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id, topic3.id],
)
get "/topics/group-topics-assigned/#{get_assigned_allowed_group_name}.json",
params: {
search: "love",
}
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to match_array(
[topic1.id],
)
end
end
describe "#messages_assigned" do
include_context "with group that is allowed to assign"
fab!(:post1, :post)
fab!(:post2, :post)
before do
add_to_assign_allowed_group(user)
Assigner.new(post1.topic, user).assign(user)
Assigner.new(post2.topic, user).assign(user2)
sign_in(user)
end
it "returns user-assigned-topics-list of given user" do
get "/topics/messages-assigned/#{user.username_lower}.json"
expect(
JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["assigned_to_user"]["id"] },
).to match_array([user.id])
end
it "returns empty user-assigned-topics-list for given user not in the assigned_allowed_group" do
get "/topics/messages-assigned/#{user2.username_lower}.json"
expect(JSON.parse(response.body)["topic_list"]["topics"]).to be_empty
end
end
describe "#private_messages_assigned" do
fab!(:group) { Fabricate(:group, users: [user]) }
fab!(:pm) { Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) }
fab!(:topic) { Fabricate(:topic, user: user) }
fab!(:pm_post) { Fabricate(:post, topic: pm) }
fab!(:post) { Fabricate(:post, topic: topic) }
before do
SiteSetting.assign_allowed_on_groups = "#{group.id}"
Fabricate(:topic_allowed_user, user: user, topic: pm)
Assigner.new(pm, user).assign(user)
Assigner.new(topic, user).assign(user)
end
it "returns assigned messages for user" do
sign_in(user)
get "/topics/private-messages-assigned/#{user.username_lower}.json"
expect(JSON.parse(response.body)["topic_list"]["topics"].map { |t| t["id"] }).to eq([pm.id])
end
end
describe "#filter" do
include_context "with group that is allowed to assign"
fab!(:group) { Fabricate(:group, assignable_level: Group::ALIAS_LEVELS[:mods_and_admins]) }
fab!(:topic_1, :topic)
fab!(:topic_2, :topic)
fab!(:topic_3, :topic)
fab!(:post_1) { Fabricate(:post, topic: topic_1) }
fab!(:post_2) { Fabricate(:post, topic: topic_2) }
fab!(:post_3) { Fabricate(:post, topic: topic_3) }
describe "when user cannot assign" do
it "ignores the assign filter" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, user).assign(user)
get "/filter", params: { q: "assigned:#{user.username_lower}", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id, topic_3.id)
end
end
describe "when user can assign" do
before { sign_in(admin) }
it "filters topics by assigned user" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, admin).assign(user)
get "/filter", params: { q: "assigned:#{user.username_lower}", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id)
end
it "filters topics by assigned group" do
Assigner.new(topic_2, admin).assign(group)
get "/filter", params: { q: "assigned:#{group.name}", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_2.id)
end
it "filters topics by multiple assigned users and groups" do
add_to_assign_allowed_group(user)
user2 = Fabricate(:user)
add_to_assign_allowed_group(user2)
# Assign topics to different users and groups
Assigner.new(topic_1, admin).assign(user)
Assigner.new(topic_2, admin).assign(group)
Assigner.new(topic_3, admin).assign(user2)
# Test filtering by multiple users and groups (comma-separated)
get "/filter",
params: {
q: "assigned:#{user.username_lower},#{group.name},#{user2.username_lower}",
format: :json,
}
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id, topic_3.id)
end
it "filters topics by subset of multiple assigned users and groups" do
add_to_assign_allowed_group(user)
user2 = Fabricate(:user)
add_to_assign_allowed_group(user2)
# Assign topics to different users and groups
Assigner.new(topic_1, admin).assign(user)
Assigner.new(topic_2, admin).assign(group)
# topic_3 remains unassigned
# Test filtering by specific users only
get "/filter",
params: {
q: "assigned:#{user.username_lower},#{user2.username_lower}",
format: :json,
}
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id)
end
it "filters topics by assigned:*" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, admin).assign(user)
Assigner.new(topic_2, admin).assign(group)
# topic_3 remains unassigned
get "/filter", params: { q: "assigned:*", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id)
end
it "filters topics by assigned:nobody" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, admin).assign(user)
Assigner.new(topic_2, admin).assign(group)
# topic_3 remains unassigned
get "/filter", params: { q: "assigned:nobody", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_3.id)
end
it "handles empty multi-value assigned filter gracefully" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, admin).assign(user)
# Test with empty values and spaces
get "/filter", params: { q: "assigned:, ,", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id, topic_3.id)
end
it "handles non-existent users and groups in multi-value filter" do
add_to_assign_allowed_group(user)
Assigner.new(topic_1, admin).assign(user)
# Test with mix of existing and non-existing users/groups
get "/filter",
params: {
q: "assigned:#{user.username_lower},nonexistent_user,nonexistent_group",
format: :json,
}
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id)
end
end
describe "when user cannot assign" do
it "ignores the assigned:* filter" do
add_to_assign_allowed_group(admin)
Assigner.new(topic_1, admin).assign(admin)
get "/filter", params: { q: "assigned:*", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id, topic_3.id)
end
it "ignores the assigned:nobody filter" do
add_to_assign_allowed_group(admin)
Assigner.new(topic_1, admin).assign(admin)
get "/filter", params: { q: "assigned:nobody", format: :json }
expect(response.status).to eq(200)
expect(
response.parsed_body.dig("topic_list", "topics").map { it["id"] },
).to contain_exactly(topic_1.id, topic_2.id, topic_3.id)
end
end
end
end