DEV: Add hashtag controller specs (#19983)

This is just cleaning up a TODO I had to add more specs
to this controller -- there are more thorough tests on the
actual HashtagService class and the type-specific hashtag
classes.
This commit is contained in:
Martin Brennan 2023-01-25 17:13:32 +10:00 committed by GitHub
parent 88a972c61b
commit 82182ec0c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 376 additions and 103 deletions

View File

@ -9,8 +9,8 @@ module CategoryHashtag
# TODO (martin) Remove this when enable_experimental_hashtag_autocomplete
# becomes the norm, it is reimplemented below for CategoryHashtagDataSourcee
def query_from_hashtag_slug(category_slug)
slug_path = category_slug.split(SEPARATOR)
return nil if slug_path.empty? || slug_path.size > 2
slug_path = split_slug_path(category_slug)
return if slug_path.blank?
slug_path.map! { |slug| CGI.escape(slug) } if SiteSetting.slug_generation_method == "encoded"
@ -34,7 +34,9 @@ module CategoryHashtag
category_slugs
.map(&:downcase)
.map do |slug|
slug_path = slug.split(":")
slug_path = split_slug_path(slug)
next if slug_path.blank?
if SiteSetting.slug_generation_method == "encoded"
slug_path.map! { |slug| CGI.escape(slug) }
end
@ -60,5 +62,11 @@ module CategoryHashtag
end
.compact
end
def split_slug_path(slug)
slug_path = slug.split(SEPARATOR)
return if slug_path.empty? || slug_path.size > 2
slug_path
end
end
end

View File

@ -1,11 +1,13 @@
# frozen_string_literal: true
RSpec.describe HashtagsController do
fab!(:category) { Fabricate(:category) }
fab!(:tag) { Fabricate(:tag) }
fab!(:category) { Fabricate(:category, name: "Random", slug: "random") }
fab!(:tag) { Fabricate(:tag, name: "bug") }
fab!(:group) { Fabricate(:group) }
fab!(:private_category) { Fabricate(:private_category, group: group) }
fab!(:private_category) do
Fabricate(:private_category, group: group, name: "Staff", slug: "staff")
end
fab!(:hidden_tag) { Fabricate(:tag, name: "hidden") }
let(:tag_group) do
@ -14,134 +16,397 @@ RSpec.describe HashtagsController do
before do
SiteSetting.tagging_enabled = true
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
SiteSetting.enable_experimental_hashtag_autocomplete = false
tag_group
end
describe "#lookup" do
context "when logged in" do
context "as regular user" do
before { sign_in(Fabricate(:user)) }
context "when enable_experimental_hashtag_autocomplete disabled" do
# TODO (martin) Remove when enable_experimental_hashtag_autocomplete is default for all sites
before { SiteSetting.enable_experimental_hashtag_autocomplete = false }
context "when logged in" do
context "as regular user" do
before { sign_in(Fabricate(:user)) }
it "returns only valid categories and tags" do
get "/hashtags.json",
params: {
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
}
it "returns only valid categories and tags" do
get "/hashtags.json",
params: {
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
}
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
category.slug => category.url,
},
"tags" => {
tag.name => tag.full_url,
},
)
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
category.slug => category.url,
},
"tags" => {
tag.name => tag.full_url,
},
)
end
it "handles tags with the TAG_HASHTAG_POSTFIX" do
get "/hashtags.json",
params: {
slugs: ["#{tag.name}#{PrettyText::Helpers::TAG_HASHTAG_POSTFIX}"],
}
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
},
"tags" => {
tag.name => tag.full_url,
},
)
end
it "does not return restricted categories or hidden tags" do
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
expect(response.status).to eq(200)
expect(response.parsed_body).to eq("categories" => {}, "tags" => {})
end
end
it "handles tags with the TAG_HASHTAG_POSTFIX" do
get "/hashtags.json",
params: {
slugs: ["#{tag.name}#{PrettyText::Helpers::TAG_HASHTAG_POSTFIX}"],
}
context "as admin" do
fab!(:admin) { Fabricate(:admin) }
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
},
"tags" => {
tag.name => tag.full_url,
},
)
before { sign_in(admin) }
it "returns restricted categories and hidden tags" do
group.add(admin)
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
private_category.slug => private_category.url,
},
"tags" => {
hidden_tag.name => hidden_tag.full_url,
},
)
end
end
it "does not return restricted categories or hidden tags" do
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
context "with sub-sub-categories" do
before do
SiteSetting.max_category_nesting = 3
sign_in(Fabricate(:user))
end
expect(response.status).to eq(200)
expect(response.parsed_body).to eq("categories" => {}, "tags" => {})
it "works" do
foo = Fabricate(:category_with_definition, slug: "foo")
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
foobarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
qux = Fabricate(:category_with_definition, slug: "qux")
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
quxbarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
invalid_slugs = [":"]
child_slugs = %w[bar baz]
deeply_nested_slugs = %w[foo:bar:baz qux:bar:baz]
get "/hashtags.json",
params: {
slugs:
invalid_slugs + child_slugs + deeply_nested_slugs +
%w[foo foo:bar bar:baz qux qux:bar],
}
expect(response.status).to eq(200)
expect(response.parsed_body["categories"]).to eq(
"foo" => foo.url,
"foo:bar" => foobar.url,
"bar:baz" => foobarbaz.id < quxbarbaz.id ? foobarbaz.url : quxbarbaz.url,
"qux" => qux.url,
"qux:bar" => quxbar.url,
)
end
end
end
context "as admin" do
fab!(:admin) { Fabricate(:admin) }
before { sign_in(admin) }
it "returns restricted categories and hidden tags" do
group.add(admin)
get "/hashtags.json", params: { slugs: [private_category.slug, hidden_tag.name] }
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
"categories" => {
private_category.slug => private_category.url,
},
"tags" => {
hidden_tag.name => hidden_tag.full_url,
},
)
context "when not logged in" do
it "returns invalid access" do
get "/hashtags.json", params: { slugs: [] }
expect(response.status).to eq(403)
end
end
end
context "with sub-sub-categories" do
before do
SiteSetting.max_category_nesting = 3
sign_in(Fabricate(:user))
end
context "when enable_experimental_hashtag_autocomplete enabled" do
before { SiteSetting.enable_experimental_hashtag_autocomplete = true }
it "works" do
foo = Fabricate(:category_with_definition, slug: "foo")
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
foobarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
context "when logged in" do
context "as regular user" do
before { sign_in(Fabricate(:user)) }
qux = Fabricate(:category_with_definition, slug: "qux")
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
quxbarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
it "returns only valid categories and tags" do
get "/hashtags.json",
params: {
slugs: [category.slug, private_category.slug, "none", tag.name, hidden_tag.name],
order: %w[category tag],
}
get "/hashtags.json",
params: {
slugs: [
":", # should not work
"foo",
"bar", # should not work
"baz", # should not work
"foo:bar",
"bar:baz",
"foo:bar:baz", # should not work
"qux",
"qux:bar",
"qux:bar:baz", # should not work
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
{
"category" => [
{
"relative_url" => category.url,
"text" => category.name,
"description" => nil,
"icon" => "folder",
"type" => "category",
"ref" => category.slug,
"slug" => category.slug,
},
],
}
"tag" => [
{
"relative_url" => tag.url,
"text" => tag.name,
"description" => nil,
"icon" => "tag",
"type" => "tag",
"ref" => tag.name,
"slug" => tag.name,
"secondary_text" => "x0",
},
],
},
)
end
expect(response.status).to eq(200)
expect(response.parsed_body["categories"]).to eq(
"foo" => foo.url,
"foo:bar" => foobar.url,
"bar:baz" => foobarbaz.id < quxbarbaz.id ? foobarbaz.url : quxbarbaz.url,
"qux" => qux.url,
"qux:bar" => quxbar.url,
)
it "handles tags with the ::tag type suffix" do
get "/hashtags.json", params: { slugs: ["#{tag.name}::tag"], order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
{
"category" => [],
"tag" => [
{
"relative_url" => tag.url,
"text" => tag.name,
"description" => nil,
"icon" => "tag",
"type" => "tag",
"ref" => "#{tag.name}::tag",
"slug" => tag.name,
"secondary_text" => "x0",
},
],
},
)
end
it "does not return restricted categories or hidden tags" do
get "/hashtags.json",
params: {
slugs: [private_category.slug, hidden_tag.name],
order: %w[category tag],
}
expect(response.status).to eq(200)
expect(response.parsed_body).to eq({ "category" => [], "tag" => [] })
end
end
context "as admin" do
fab!(:admin) { Fabricate(:admin) }
before { sign_in(admin) }
it "returns restricted categories and hidden tags" do
group.add(admin)
get "/hashtags.json",
params: {
slugs: [private_category.slug, hidden_tag.name],
order: %w[category tag],
}
expect(response.status).to eq(200)
expect(response.parsed_body).to eq(
{
"category" => [
{
"relative_url" => private_category.url,
"text" => private_category.name,
"description" => nil,
"icon" => "folder",
"type" => "category",
"ref" => private_category.slug,
"slug" => private_category.slug,
},
],
"tag" => [
{
"relative_url" => hidden_tag.url,
"text" => hidden_tag.name,
"description" => nil,
"icon" => "tag",
"type" => "tag",
"ref" => hidden_tag.name,
"slug" => hidden_tag.name,
"secondary_text" => "x0",
},
],
},
)
end
end
context "with sub-sub-categories" do
before do
SiteSetting.max_category_nesting = 3
sign_in(Fabricate(:user))
end
it "works" do
foo = Fabricate(:category_with_definition, slug: "foo")
foobar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: foo.id)
foobarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: foobar.id)
qux = Fabricate(:category_with_definition, slug: "qux")
quxbar = Fabricate(:category_with_definition, slug: "bar", parent_category_id: qux.id)
quxbarbaz =
Fabricate(:category_with_definition, slug: "baz", parent_category_id: quxbar.id)
invalid_slugs = [":"]
child_slugs = %w[bar baz]
deeply_nested_slugs = %w[foo:bar:baz qux:bar:baz]
get "/hashtags.json",
params: {
slugs:
invalid_slugs + child_slugs + deeply_nested_slugs +
%w[foo foo:bar bar:baz qux qux:bar],
order: %w[category tag],
}
expect(response.status).to eq(200)
found_categories = response.parsed_body["category"]
expect(found_categories.map { |c| c["ref"] }).to match_array(
%w[foo foo:bar bar:baz qux qux:bar],
)
expect(found_categories.find { |c| c["ref"] == "foo" }["relative_url"]).to eq(foo.url)
expect(found_categories.find { |c| c["ref"] == "foo:bar" }["relative_url"]).to eq(
foobar.url,
)
expect(found_categories.find { |c| c["ref"] == "bar:baz" }["relative_url"]).to eq(
foobarbaz.id < quxbarbaz.id ? foobarbaz.url : quxbarbaz.url,
)
expect(found_categories.find { |c| c["ref"] == "qux" }["relative_url"]).to eq(qux.url)
expect(found_categories.find { |c| c["ref"] == "qux:bar" }["relative_url"]).to eq(
quxbar.url,
)
end
end
end
context "when not logged in" do
it "returns invalid access" do
get "/hashtags.json", params: { slugs: [], order: %w[category tag] }
expect(response.status).to eq(403)
end
end
end
end
describe "#search" do
fab!(:tag_2) { Fabricate(:tag, name: "random") }
context "when logged in" do
before { sign_in(Fabricate(:user)) }
it "returns the found category and then tag" do
get "/hashtags/search.json", params: { term: "rand", order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body["results"]).to eq(
[
{
"relative_url" => category.url,
"text" => category.name,
"description" => nil,
"icon" => "folder",
"type" => "category",
"ref" => category.slug,
"slug" => category.slug,
},
{
"relative_url" => tag_2.url,
"text" => tag_2.name,
"description" => nil,
"icon" => "tag",
"type" => "tag",
"ref" => "#{tag_2.name}::tag",
"slug" => tag_2.name,
"secondary_text" => "x0",
},
],
)
end
it "does not return hidden and restricted categories/tags" do
get "/hashtags/search.json", params: { term: "staff", order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body["results"]).to eq([])
get "/hashtags/search.json", params: { term: "hidden", order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body["results"]).to eq([])
end
end
context "when logged in as admin" do
before { sign_in(Fabricate(:admin)) }
it "returns hidden and restricted categories/tags" do
get "/hashtags/search.json", params: { term: "staff", order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body["results"]).to eq(
[
{
"relative_url" => private_category.url,
"text" => private_category.name,
"description" => nil,
"icon" => "folder",
"type" => "category",
"ref" => private_category.slug,
"slug" => private_category.slug,
},
],
)
get "/hashtags/search.json", params: { term: "hidden", order: %w[category tag] }
expect(response.status).to eq(200)
expect(response.parsed_body["results"]).to eq(
[
{
"relative_url" => hidden_tag.url,
"text" => hidden_tag.name,
"description" => nil,
"icon" => "tag",
"type" => "tag",
"ref" => "#{hidden_tag.name}",
"slug" => hidden_tag.name,
"secondary_text" => "x0",
},
],
)
end
end
context "when not logged in" do
it "returns invalid access" do
get "/hashtags.json", params: { slugs: [] }
get "/hashtags/search.json", params: { term: "rand", order: %w[category tag] }
expect(response.status).to eq(403)
end
end
end
# TODO (martin) write a spec here for the new
# #lookup behaviour and the new #search behaviour
end