mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 02:03:40 +08:00
5683c90917
Our code assumed the content_range interval was inclusive, but they are open-ended due to Postgres' [discrete range types](https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-DISCRETE), meaning [1,2] will be represented as [1,3). It also fixes some flaky tests due to test data not being correctly setup and the registry not being resetted after each test.
203 lines
6.5 KiB
Ruby
203 lines
6.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
describe TopicSummarization do
|
|
fab!(:user) { Fabricate(:admin) }
|
|
fab!(:topic) { Fabricate(:topic, highest_post_number: 2) }
|
|
fab!(:post_1) { Fabricate(:post, topic: topic, post_number: 1) }
|
|
fab!(:post_2) { Fabricate(:post, topic: topic, post_number: 2) }
|
|
|
|
shared_examples "includes only public-visible topics" do
|
|
subject { described_class.new(DummyCustomSummarization.new({})) }
|
|
|
|
it "only includes visible posts" do
|
|
topic.first_post.update!(hidden: true)
|
|
|
|
posts = subject.summary_targets(topic)
|
|
|
|
expect(posts.none?(&:hidden?)).to eq(true)
|
|
end
|
|
|
|
it "doesn't include posts without users" do
|
|
topic.first_post.user.destroy!
|
|
|
|
posts = subject.summary_targets(topic)
|
|
|
|
expect(posts.detect { |p| p.id == topic.first_post.id }).to be_nil
|
|
end
|
|
|
|
it "doesn't include deleted posts" do
|
|
topic.first_post.update!(user_id: nil)
|
|
|
|
posts = subject.summary_targets(topic)
|
|
|
|
expect(posts.detect { |p| p.id == topic.first_post.id }).to be_nil
|
|
end
|
|
end
|
|
|
|
describe "#summary_targets" do
|
|
context "when the topic has a best replies summary" do
|
|
before { topic.has_summary = true }
|
|
|
|
it_behaves_like "includes only public-visible topics"
|
|
end
|
|
|
|
context "when the topic doesn't have a best replies summary" do
|
|
before { topic.has_summary = false }
|
|
|
|
it_behaves_like "includes only public-visible topics"
|
|
end
|
|
end
|
|
|
|
describe "#summarize" do
|
|
subject(:summarization) { described_class.new(strategy) }
|
|
|
|
let(:strategy) { DummyCustomSummarization.new(summary) }
|
|
|
|
def assert_summary_is_cached(topic, summary_response)
|
|
cached_summary = SummarySection.find_by(target: topic, meta_section_id: nil)
|
|
|
|
expect(cached_summary.content_range).to cover(*topic.posts.map(&:post_number))
|
|
expect(cached_summary.summarized_text).to eq(summary_response[:summary])
|
|
expect(cached_summary.original_content_sha).to be_present
|
|
expect(cached_summary.algorithm).to eq(strategy.model)
|
|
end
|
|
|
|
def assert_chunk_is_cached(topic, chunk_response)
|
|
cached_chunk =
|
|
SummarySection
|
|
.where.not(meta_section_id: nil)
|
|
.find_by(
|
|
target: topic,
|
|
content_range: (chunk_response[:ids].min..chunk_response[:ids].max),
|
|
)
|
|
|
|
expect(cached_chunk.summarized_text).to eq(chunk_response[:summary])
|
|
expect(cached_chunk.original_content_sha).to be_present
|
|
expect(cached_chunk.algorithm).to eq(strategy.model)
|
|
end
|
|
|
|
context "when the content was summarized in a single chunk" do
|
|
let(:summary) { { summary: "This is the final summary", chunks: [] } }
|
|
|
|
it "caches the summary" do
|
|
section = summarization.summarize(topic, user)
|
|
|
|
expect(section.summarized_text).to eq(summary[:summary])
|
|
|
|
assert_summary_is_cached(topic, summary)
|
|
end
|
|
|
|
it "returns the cached version in subsequent calls" do
|
|
summarization.summarize(topic, user)
|
|
|
|
cached_summary_text = "This is a cached summary"
|
|
cached_summary =
|
|
SummarySection.find_by(target: topic, meta_section_id: nil).update!(
|
|
summarized_text: cached_summary_text,
|
|
updated_at: 24.hours.ago,
|
|
)
|
|
|
|
section = summarization.summarize(topic, user)
|
|
expect(section.summarized_text).to eq(cached_summary_text)
|
|
end
|
|
end
|
|
|
|
context "when the content was summarized in multiple chunks" do
|
|
let(:summary) do
|
|
{
|
|
summary: "This is the final summary",
|
|
chunks: [
|
|
{ ids: [topic.first_post.post_number], summary: "this is the first chunk" },
|
|
{ ids: [post_1.post_number, post_2.post_number], summary: "this is the second chunk" },
|
|
],
|
|
}
|
|
end
|
|
|
|
it "caches the summary and each chunk" do
|
|
section = summarization.summarize(topic, user)
|
|
|
|
expect(section.summarized_text).to eq(summary[:summary])
|
|
|
|
assert_summary_is_cached(topic, summary)
|
|
|
|
summary[:chunks].each { |c| assert_chunk_is_cached(topic, c) }
|
|
end
|
|
end
|
|
|
|
describe "invalidating cached summaries" do
|
|
let(:cached_text) { "This is a cached summary" }
|
|
let(:summarized_text) { "This is the final summary" }
|
|
let(:summary) do
|
|
{
|
|
summary: summarized_text,
|
|
chunks: [
|
|
{ ids: [topic.first_post.post_number], summary: "this is the first chunk" },
|
|
{ ids: [post_1.post_number, post_2.post_number], summary: "this is the second chunk" },
|
|
],
|
|
}
|
|
end
|
|
|
|
def cached_summary
|
|
SummarySection.find_by(target: topic, meta_section_id: nil)
|
|
end
|
|
|
|
before do
|
|
summarization.summarize(topic, user)
|
|
|
|
cached_summary.update!(summarized_text: cached_text, created_at: 24.hours.ago)
|
|
end
|
|
|
|
context "when the user can requests new summaries" do
|
|
context "when there are no new posts" do
|
|
it "returns the cached summary" do
|
|
section = summarization.summarize(topic, user)
|
|
|
|
expect(section.summarized_text).to eq(cached_text)
|
|
end
|
|
end
|
|
|
|
context "when there are new posts" do
|
|
before { cached_summary.update!(original_content_sha: "outdated_sha") }
|
|
|
|
it "returns a new summary" do
|
|
section = summarization.summarize(topic, user)
|
|
|
|
expect(section.summarized_text).to eq(summarized_text)
|
|
end
|
|
|
|
context "when the cached summary is less than one hour old" do
|
|
before { cached_summary.update!(created_at: 30.minutes.ago) }
|
|
|
|
it "returns the cached summary" do
|
|
cached_summary.update!(created_at: 30.minutes.ago)
|
|
|
|
section = summarization.summarize(topic, user)
|
|
|
|
expect(section.summarized_text).to eq(cached_text)
|
|
expect(section.outdated).to eq(true)
|
|
end
|
|
|
|
it "returns a new summary if the skip_age_check flag is passed" do
|
|
section = summarization.summarize(topic, user, skip_age_check: true)
|
|
|
|
expect(section.summarized_text).to eq(summarized_text)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "stream partial updates" do
|
|
let(:summary) { { summary: "This is the final summary", chunks: [] } }
|
|
|
|
it "receives a blk that is passed to the underlying strategy and called with partial summaries" do
|
|
partial_result = nil
|
|
|
|
summarization.summarize(topic, user) { |partial_summary| partial_result = partial_summary }
|
|
|
|
expect(partial_result).to eq(summary[:summary])
|
|
end
|
|
end
|
|
end
|
|
end
|