otwarchive-symphonyarchive/spec/models/search/work_query_spec.rb
2026-03-11 22:22:11 +00:00

291 lines
10 KiB
Ruby

require "spec_helper"
describe WorkQuery do
it "returns raw json for a simple search" do
q = WorkQuery.new(query: "unicorns")
search_body = q.generated_query
query = search_body.dig(:query, :bool, :must).first
expect(query.dig(:query_string, :query)).to eq("unicorns")
expect(query.dig(:query_string, :default_operator)).to eq("AND")
end
it "never returns drafts" do
q = WorkQuery.new
expect(q.filters).to include({ term: { posted: "true" } })
end
it "never returns hidden works" do
q = WorkQuery.new
expect(q.filters).to include({ term: { hidden_by_admin: "false" } })
end
it "does not return unrevealed works by default" do
q = WorkQuery.new
expect(q.filters).to include({ term: { in_unrevealed_collection: "false" } })
end
it "returns unrevealed works when filtering by a collection" do
q = WorkQuery.new(works_parent: Collection.new)
expect(q.filters).not_to include({ term: { in_unrevealed_collection: "false" } })
end
it "does not return restricted works by default" do
q = WorkQuery.new
expect(q.filters).to include({ term: { restricted: "false" } })
end
it "only returns restricted works when a user is logged in" do
User.current_user = User.new
q = WorkQuery.new
expect(q.filters).not_to include({ term: { restricted: "false" } })
end
it "does not return anonymous works when filtering by a pseud" do
q = WorkQuery.new(works_parent: Pseud.new)
expect(q.filters).to include({ term: { in_anon_collection: "false" } })
end
it "does not return anonymous works when filtering by a user" do
q = WorkQuery.new(works_parent: User.new)
expect(q.filters).to include({ term: { in_anon_collection: "false" } })
end
it "allows filtering for complete works" do
q = WorkQuery.new(complete: true)
expect(q.filters).to include({ term: { complete: true } })
end
it "allows you to filtering for works by tag" do
tag = Tag.new
tag.id = 1
q = WorkQuery.new(works_parent: tag)
# expect(q.filters).to include({terms: { execution: 'and', filter_ids: [1]} })
expect(q.filters).to include({ term: { filter_ids: 1 } })
end
it "allows filtering for works by collection" do
collection = Collection.new
collection.id = 5
q = WorkQuery.new(works_parent: collection)
expect(q.filters).to include({ terms: { collection_ids: [5] } })
end
it "allows filtering for works by pseud" do
pseud = Pseud.new
pseud.id = 42
q = WorkQuery.new(works_parent: pseud, pseud_ids: [3])
expect(q.filters).to include({ terms: { pseud_ids: [3, 42] } })
end
it "allows filtering for works by user" do
user = User.new
user.id = 2
q = WorkQuery.new(works_parent: user)
expect(q.filters).to include({ terms: { user_ids: [2] } })
end
it "allows filtering for works by tag ids" do
tag = Tag.new
tag.id = 6
q = WorkQuery.new(filter_ids: [6])
# expect(q.filters).to include({terms: { execution: 'and', filter_ids: [6]} })
expect(q.filters).to include({ term: { filter_ids: 6 } })
end
it "allows excluding works by tag ids" do
FactoryBot.create(:tag, name: "foobar", id: 4077, canonical: true, type: "Freeform")
q = WorkQuery.new(excluded_tag_names: "foobar")
search_body = q.generated_query
expect(search_body[:query][:bool][:must_not]).to include(term: { filter_ids: 4077 })
end
it "allows filtering for works by language" do
q = WorkQuery.new(language_id: "cy")
expect(q.filters).to include(term: { "language_id.keyword": "cy" })
end
it "allows filtering for works with only one chapter" do
q = WorkQuery.new(single_chapter: true)
expect(q.filters).to include({ term: { expected_number_of_chapters: 1 } })
end
it "allows filtering for works by type" do
q = WorkQuery.new(work_types: %w[Text Art])
expect(q.filters).to include({ terms: { work_type: %w[Text Art] } })
end
it "allows filtering by count ranges" do
q = WorkQuery.new(word_count: ">1000")
expect(q.filters).to include({ range: { word_count: { gt: 1000 } } })
end
it "sorts by relevance by default" do
q = WorkQuery.new
expect(q.generated_query[:sort]).to eq([{ "_score" => { order: "desc" } }, { id: { order: "desc" } }])
end
it "allows sorting by creator name" do
q = WorkQuery.new(sort_column: "authors_to_sort_on", sort_direction: "asc")
expect(q.generated_query[:sort]).to eq([{ "authors_to_sort_on" => { order: "asc" } }, { id: { order: "asc" } }])
end
it "allows sorting by title" do
q = WorkQuery.new(sort_column: "title_to_sort_on")
expect(q.generated_query[:sort]).to eq([{ "title_to_sort_on" => { order: "desc" } }, { id: { order: "desc" } }])
end
it "allows sorting by kudos" do
q = WorkQuery.new(sort_column: "kudos_count")
expect(q.generated_query[:sort]).to eq([{ "kudos_count" => { order: "desc" } }, { id: { order: "desc" } }])
end
it "allows sorting by comments" do
q = WorkQuery.new(sort_column: "comments_count")
expect(q.generated_query[:sort]).to eq([{ "comments_count" => { order: "desc" } }, { id: { order: "desc" } }])
end
it "rescues absurd relative dates" do
q = WorkQuery.new(revised_at: "> 700000000 days")
filter = q.range_filters.first
date = filter.dig(:range, :revised_at, :lt)
expect(date.year).to eq(1000.years.ago.year)
end
it "rescues absurd relative date ranges" do
q = WorkQuery.new(revised_at: "700000000-700000001 days ago")
filter = q.range_filters.first
start_date = filter.dig(:range, :revised_at, :gte)
end_date = filter.dig(:range, :revised_at, :lte)
expect(start_date.year).to eq(1000.years.ago.year)
expect(end_date.year).to eq(1000.years.ago.year)
end
it "discards unparseable absolute date ranges" do
q = WorkQuery.new(date_from: "2017-12-32")
expect(q.date_range_filter).to be_nil
q = WorkQuery.new(date_to: "many moons ago")
expect(q.date_range_filter).to be_nil
end
it "clamps absolute date ranges so the year is between 0-9999" do
q = WorkQuery.new(date_from: "2017-12-31")
expect(q.date_range_filter.dig(:range, :revised_at, :gte)).to eq(Date.new(2017, 12, 31))
q = WorkQuery.new(date_from: "-2000-12-26", date_to: "20000-11-27")
expect(q.date_range_filter.dig(:range, :revised_at, :gte)).to eq(Date.new(0, 12, 26))
expect(q.date_range_filter.dig(:range, :revised_at, :lte)).to eq(Date.new(9999, 11, 27))
end
describe "#works_per_language" do
context "when invoked by a guest" do
it "returns the count of public, revealed, and unhidden works per language code" do
es_response = {
"took" => 16,
"timed_out" => false,
"_shards" => {
"total" => 12,
"successful" => 12,
"skipped" => 0,
"failed" => 0
},
"hits" => {
"total" => { "value" => 10_000, "relation" => "gte" },
"max_score" => nil,
"hits" => []
},
"aggregations" => {
"languages" => {
"doc_count_error_upper_bound" => 0,
"sum_other_doc_count" => 0,
"buckets" => [
{ "key" => "en", "doc_count" => 863_269 },
{ "key" => "ru", "doc_count" => 11_476 },
{ "key" => "zh", "doc_count" => 1622 }
]
}
}
}
allow($elasticsearch).to receive(:search)
.with(index: "ao3_test_works", body: {
size: 0,
query: {
bool: {
filter: [
{ term: { posted: "true" } },
{ term: { hidden_by_admin: "false" } },
{ term: { restricted: "false" } },
{ term: { in_unrevealed_collection: "false" } }
]
}
},
aggregations: {
languages: {
terms: { field: "language_id.keyword", size: 100 }
}
}
})
.and_return(es_response)
result = WorkQuery.new.works_per_language(100)
expect(result).to eq({ "en" => 863_269, "ru" => 11_476, "zh" => 1622 })
end
end
context "when invoked by a registered user" do
before do
User.current_user = build(:user)
end
it "returns the count of public, restricted, revealed, and unhidden works works per language code" do
es_response = {
"took" => 16,
"timed_out" => false,
"_shards" => {
"total" => 12,
"successful" => 12,
"skipped" => 0,
"failed" => 0
},
"hits" => {
"total" => { "value" => 10_000, "relation" => "gte" },
"max_score" => nil,
"hits" => []
},
"aggregations" => {
"languages" => {
"doc_count_error_upper_bound" => 0,
"sum_other_doc_count" => 0,
"buckets" => [
{ "key" => "en", "doc_count" => 863_269 },
{ "key" => "ru", "doc_count" => 11_476 },
{ "key" => "zh", "doc_count" => 1622 }
]
}
}
}
allow($elasticsearch).to receive(:search)
.with(index: "ao3_test_works", body: {
size: 0,
query: {
bool: {
filter: [
{ term: { posted: "true" } },
{ term: { hidden_by_admin: "false" } },
{ term: { in_unrevealed_collection: "false" } }
]
}
},
aggregations: {
languages: {
terms: { field: "language_id.keyword", size: 100 }
}
}
})
.and_return(es_response)
result = WorkQuery.new.works_per_language(100)
expect(result).to eq({ "en" => 863_269, "ru" => 11_476, "zh" => 1622 })
end
end
end
end