2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2025-08-17 18:04:11 +08:00

DEV: Resolve RSpec/NamedSubject lint issues in d-ai (#33810)

This commit is contained in:
Jarek Radosz 2025-07-24 13:50:14 +02:00 committed by GitHub
parent 1cb3e2c070
commit e7f856169a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 216 additions and 168 deletions

View file

@ -43,7 +43,3 @@ RSpec/InstanceVariable:
Enabled: true
Include:
- spec/models/**/*
RSpec/NamedSubject:
Exclude:
- plugins/discourse-ai/**/*

View file

@ -5,6 +5,8 @@ require Rails.root.join(
)
RSpec.describe FixBrokenOpenAiEmbeddingsConfig do
subject(:migration) { described_class.new }
let(:connection) { ActiveRecord::Base.connection }
def store_setting(name, val)
@ -29,7 +31,7 @@ RSpec.describe FixBrokenOpenAiEmbeddingsConfig do
before { store_setting("ai_embeddings_selected_model", embedding_definition.id) }
it "does nothing" do
subject.up
migration.up
expect(configured_model_id).to eq(embedding_definition.id.to_s)
end
@ -37,7 +39,7 @@ RSpec.describe FixBrokenOpenAiEmbeddingsConfig do
context "when there is no previous config" do
it "does nothing" do
subject.up
migration.up
expect(configured_model_id).to be_blank
end
@ -50,7 +52,7 @@ RSpec.describe FixBrokenOpenAiEmbeddingsConfig do
end
it "does nothing" do
subject.up
migration.up
expect(configured_model_id).to be_blank
end
@ -63,7 +65,7 @@ RSpec.describe FixBrokenOpenAiEmbeddingsConfig do
end
it "copies the config" do
subject.up
migration.up
embedding_def = EmbeddingDefinition.last

View file

@ -5,6 +5,8 @@ require Rails.root.join(
)
RSpec.describe CleanUnusedEmbeddingSearchIndexes do
subject(:migration) { described_class.new }
let(:connection) { ActiveRecord::Base.connection }
before { enable_current_plugin }
@ -41,7 +43,7 @@ RSpec.describe CleanUnusedEmbeddingSearchIndexes do
context "when there are no embedding definitions" do
it "removes all indexes" do
subject.up
migration.up
remaininig_idxs =
DB.query_single(
@ -62,7 +64,7 @@ RSpec.describe CleanUnusedEmbeddingSearchIndexes do
memo << "ai_#{type}_embeddings_2_1_search_bit"
end
subject.up
migration.up
remaininig_idxs =
DB.query_single(
@ -87,7 +89,7 @@ RSpec.describe CleanUnusedEmbeddingSearchIndexes do
memo << "ai_#{type}_embeddings_3_1_search_bit"
end
subject.up
migration.up
remaininig_idxs =
DB.query_single(
@ -122,7 +124,7 @@ RSpec.describe CleanUnusedEmbeddingSearchIndexes do
memo << "ai_#{type}_embeddings_9_1_search_bit"
end
subject.up
migration.up
other_idxs =
DB.query_single(

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::DigestRagUpload do
subject(:job) { described_class.new }
fab!(:persona) { Fabricate(:ai_persona) }
fab!(:upload) { Fabricate(:upload, extension: "txt") }
fab!(:image_upload) { Fabricate(:upload, extension: "png") }
@ -80,11 +82,7 @@ RSpec.describe Jobs::DigestRagUpload do
before { File.expects(:open).returns(document_file) }
it "splits an upload into chunks" do
subject.execute(
upload_id: upload.id,
target_id: persona.id,
target_type: persona.class.to_s,
)
job.execute(upload_id: upload.id, target_id: persona.id, target_type: persona.class.to_s)
created_fragment = RagDocumentFragment.last
@ -95,11 +93,7 @@ RSpec.describe Jobs::DigestRagUpload do
it "queue jobs to generate embeddings for each fragment" do
expect {
subject.execute(
upload_id: upload.id,
target_id: persona.id,
target_type: persona.class.to_s,
)
job.execute(upload_id: upload.id, target_id: persona.id, target_type: persona.class.to_s)
}.to change(Jobs::GenerateRagEmbeddings.jobs, :size).by(1)
end
end
@ -109,7 +103,7 @@ RSpec.describe Jobs::DigestRagUpload do
previous_count = RagDocumentFragment.where(upload: upload, target: persona).count
subject.execute(upload_id: upload.id, target_id: persona.id, target_type: persona.class.to_s)
job.execute(upload_id: upload.id, target_id: persona.id, target_type: persona.class.to_s)
updated_count = RagDocumentFragment.where(upload: upload, target: persona).count
expect(updated_count).to eq(previous_count)

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::FastTrackTopicGist do
subject(:job) { described_class.new }
describe "#execute" do
fab!(:topic_1) { Fabricate(:topic) }
fab!(:post_1) { Fabricate(:post, topic: topic_1, post_number: 1) }
@ -28,7 +30,7 @@ RSpec.describe Jobs::FastTrackTopicGist do
context "when it's up to date" do
it "does nothing" do
DiscourseAi::Completions::Llm.with_prepared_responses([updated_gist]) do
subject.execute(topic_id: topic_1.id)
job.execute(topic_id: topic_1.id)
end
gist = AiSummary.gist.find_by(target: topic_1)
@ -42,7 +44,7 @@ RSpec.describe Jobs::FastTrackTopicGist do
it "regenerates the gist using the latest data" do
DiscourseAi::Completions::Llm.with_prepared_responses([updated_gist]) do
subject.execute(topic_id: topic_1.id)
job.execute(topic_id: topic_1.id)
end
gist = AiSummary.gist.find_by(target: topic_1)
@ -55,7 +57,7 @@ RSpec.describe Jobs::FastTrackTopicGist do
ai_gist.update!(created_at: 2.minutes.ago)
DiscourseAi::Completions::Llm.with_prepared_responses([updated_gist]) do
subject.execute(topic_id: topic_1.id)
job.execute(topic_id: topic_1.id)
end
gist = AiSummary.gist.find_by(target: topic_1)
@ -68,7 +70,7 @@ RSpec.describe Jobs::FastTrackTopicGist do
context "when the topic doesn't have a hot topic score" do
it "creates gist" do
subject.execute(topic_id: topic_1.id)
job.execute(topic_id: topic_1.id)
gist = AiSummary.gist.find_by(target: topic_1)
expect(gist).to be_present
@ -79,7 +81,7 @@ RSpec.describe Jobs::FastTrackTopicGist do
before { TopicHotScore.create!(topic_id: topic_1.id, score: 0.1) }
it "creates gist" do
subject.execute(topic_id: topic_1.id)
job.execute(topic_id: topic_1.id)
gist = AiSummary.gist.find_by(target: topic_1)
expect(gist).to be_present

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::GenerateInferredConcepts do
subject(:job) { described_class.new }
fab!(:topic)
fab!(:post)
fab!(:concept) { Fabricate(:inferred_concept, name: "programming") }
@ -16,8 +18,8 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_topic_to_concepts,
)
subject.execute(item_type: "topics", item_ids: [])
subject.execute(item_type: "topics", item_ids: nil)
job.execute(item_type: "topics", item_ids: [])
job.execute(item_type: "topics", item_ids: nil)
end
it "does nothing with blank item_type" do
@ -25,14 +27,14 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_topic_to_concepts,
)
subject.execute(item_type: "", item_ids: [topic.id])
subject.execute(item_type: nil, item_ids: [topic.id])
job.execute(item_type: "", item_ids: [topic.id])
job.execute(item_type: nil, item_ids: [topic.id])
end
it "validates item_type to be topics or posts" do
allow(Rails.logger).to receive(:error).with(/Invalid item_type/)
subject.execute(item_type: "invalid", item_ids: [1])
job.execute(item_type: "invalid", item_ids: [1])
end
context "with topics" do
@ -41,7 +43,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_topic_to_concepts,
).with(topic)
subject.execute(item_type: "topics", item_ids: [topic.id], match_only: true)
job.execute(item_type: "topics", item_ids: [topic.id], match_only: true)
end
it "processes topics in generation mode" do
@ -49,7 +51,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:generate_concepts_from_topic,
).with(topic)
subject.execute(item_type: "topics", item_ids: [topic.id], match_only: false)
job.execute(item_type: "topics", item_ids: [topic.id], match_only: false)
end
it "handles topics that don't exist" do
@ -58,7 +60,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_topic_to_concepts,
)
subject.execute(
job.execute(
item_type: "topics",
item_ids: [999_999], # non-existent ID
match_only: true,
@ -74,7 +76,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
allow(manager_instance).to receive(:match_topic_to_concepts).with(topic)
allow(manager_instance).to receive(:match_topic_to_concepts).with(topic2)
subject.execute(item_type: "topics", item_ids: [topic.id, topic2.id], match_only: true)
job.execute(item_type: "topics", item_ids: [topic.id, topic2.id], match_only: true)
end
it "processes topics in batches" do
@ -85,7 +87,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
allow(Topic).to receive(:where).with(id: topic_ids[0..2]).and_call_original
allow(Topic).to receive(:where).with(id: topic_ids[3..4]).and_call_original
subject.execute(item_type: "topics", item_ids: topic_ids, batch_size: 3, match_only: true)
job.execute(item_type: "topics", item_ids: topic_ids, batch_size: 3, match_only: true)
end
end
@ -95,7 +97,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_post_to_concepts,
).with(post)
subject.execute(item_type: "posts", item_ids: [post.id], match_only: true)
job.execute(item_type: "posts", item_ids: [post.id], match_only: true)
end
it "processes posts in generation mode" do
@ -103,7 +105,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:generate_concepts_from_post,
).with(post)
subject.execute(item_type: "posts", item_ids: [post.id], match_only: false)
job.execute(item_type: "posts", item_ids: [post.id], match_only: false)
end
it "handles posts that don't exist" do
@ -112,7 +114,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
:match_post_to_concepts,
)
subject.execute(
job.execute(
item_type: "posts",
item_ids: [999_999], # non-existent ID
match_only: true,
@ -128,7 +130,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
allow(manager_instance).to receive(:match_post_to_concepts).with(post)
allow(manager_instance).to receive(:match_post_to_concepts).with(post2)
subject.execute(item_type: "posts", item_ids: [post.id, post2.id], match_only: true)
job.execute(item_type: "posts", item_ids: [post.id, post2.id], match_only: true)
end
end
@ -141,7 +143,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
/Error generating concepts from topic #{topic.id}/,
)
subject.execute(item_type: "topics", item_ids: [topic.id], match_only: true)
job.execute(item_type: "topics", item_ids: [topic.id], match_only: true)
end
it "uses default batch size of 100" do
@ -152,7 +154,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
allow(Topic).to receive(:where).with(id: topic_ids[0..99]).and_call_original
allow(Topic).to receive(:where).with(id: topic_ids[100..149]).and_call_original
subject.execute(item_type: "topics", item_ids: topic_ids, match_only: true)
job.execute(item_type: "topics", item_ids: topic_ids, match_only: true)
end
it "respects custom batch size" do
@ -164,7 +166,7 @@ RSpec.describe Jobs::GenerateInferredConcepts do
allow(Topic).to receive(:where).with(id: topic_ids[2..3]).and_call_original
allow(Topic).to receive(:where).with(id: topic_ids[4..4]).and_call_original
subject.execute(item_type: "topics", item_ids: topic_ids, batch_size: 2, match_only: true)
job.execute(item_type: "topics", item_ids: topic_ids, batch_size: 2, match_only: true)
end
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::GenerateRagEmbeddings do
subject(:job) { described_class.new }
before { enable_current_plugin }
describe "#execute" do
@ -29,7 +31,7 @@ RSpec.describe Jobs::GenerateRagEmbeddings do
it "generates a new vector for each fragment" do
expected_embeddings = 2
subject.execute(fragment_ids: [rag_document_fragment_1.id, rag_document_fragment_2.id])
job.execute(fragment_ids: [rag_document_fragment_1.id, rag_document_fragment_2.id])
embeddings_count =
DB.query_single(
@ -43,7 +45,7 @@ RSpec.describe Jobs::GenerateRagEmbeddings do
it "sends an update through mb after a batch finishes" do
updates =
MessageBus.track_publish("/discourse-ai/rag/#{rag_document_fragment_1.upload_id}") do
subject.execute(fragment_ids: [rag_document_fragment_1.id])
job.execute(fragment_ids: [rag_document_fragment_1.id])
end
upload_index_stats = updates.last.data

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::ManageEmbeddingDefSearchIndex do
subject(:job) { described_class.new }
fab!(:embedding_definition)
before { enable_current_plugin }
@ -10,7 +12,7 @@ RSpec.describe Jobs::ManageEmbeddingDefSearchIndex do
it "does nothing" do
invalid_id = 999_999_999
subject.execute(id: invalid_id)
job.execute(id: invalid_id)
expect(
DiscourseAi::Embeddings::Schema.correctly_indexed?(
@ -22,14 +24,14 @@ RSpec.describe Jobs::ManageEmbeddingDefSearchIndex do
context "when the embedding def is fresh" do
it "creates the indexes" do
subject.execute(id: embedding_definition.id)
job.execute(id: embedding_definition.id)
expect(DiscourseAi::Embeddings::Schema.correctly_indexed?(embedding_definition)).to eq(true)
end
it "creates them only once" do
subject.execute(id: embedding_definition.id)
subject.execute(id: embedding_definition.id)
job.execute(id: embedding_definition.id)
job.execute(id: embedding_definition.id)
expect(DiscourseAi::Embeddings::Schema.correctly_indexed?(embedding_definition)).to eq(true)
end
@ -46,7 +48,7 @@ RSpec.describe Jobs::ManageEmbeddingDefSearchIndex do
false,
)
subject.execute(id: embedding_definition.id)
job.execute(id: embedding_definition.id)
expect(DiscourseAi::Embeddings::Schema.correctly_indexed?(embedding_definition)).to eq(
true,

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::GenerateConceptsFromPopularItems do
subject(:job) { described_class.new }
fab!(:topic) { Fabricate(:topic, posts_count: 6, views: 150, like_count: 12) }
fab!(:post) { Fabricate(:post, like_count: 8, post_number: 2) }
@ -29,7 +31,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
)
allow(Jobs).to receive(:enqueue)
subject.execute({})
job.execute({})
end
it "processes popular topics when enabled" do
@ -57,7 +59,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
:find_candidate_posts,
).and_return([])
subject.execute({})
job.execute({})
end
end
@ -85,7 +87,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
batch_size: 10,
)
subject.execute({})
job.execute({})
end
end
@ -136,7 +138,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
match_only: true,
)
subject.execute({})
job.execute({})
end
it "does not schedule jobs when no candidates found" do
@ -150,7 +152,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
allow(Jobs).to receive(:enqueue)
allow(Jobs).to receive(:enqueue_in)
subject.execute({})
job.execute({})
end
it "uses site setting values for topic filtering" do
@ -175,7 +177,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
:find_candidate_posts,
).and_return([])
subject.execute({})
job.execute({})
end
end
@ -198,7 +200,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
created_after: 45.days.ago,
).and_return([])
subject.execute({})
job.execute({})
end
end
@ -231,7 +233,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
created_after: 30.days.ago, # default from before block
).and_return([])
subject.execute({})
job.execute({})
end
end
@ -248,7 +250,7 @@ RSpec.describe Jobs::GenerateConceptsFromPopularItems do
allow(Jobs).to receive(:enqueue).twice
subject.execute({})
job.execute({})
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::RemoveOrphanedEmbeddings do
subject(:job) { described_class.new }
before { enable_current_plugin }
describe "#execute" do
@ -40,7 +42,7 @@ RSpec.describe Jobs::RemoveOrphanedEmbeddings do
embedding_definition_2.id,
)
subject.execute({})
job.execute({})
expect(find_all_embeddings_of(topic, "ai_topics_embeddings", "topic_id")).to contain_exactly(
embedding_definition_2.id,
@ -54,7 +56,7 @@ RSpec.describe Jobs::RemoveOrphanedEmbeddings do
expect(DiscourseAi::Embeddings::Schema.correctly_indexed?(embedding_definition)).to eq(true)
expect(DiscourseAi::Embeddings::Schema.correctly_indexed?(embedding_definition_2)).to eq(true)
subject.execute({})
job.execute({})
index_names =
DiscourseAi::Embeddings::Schema::EMBEDDING_TARGETS.map do |t|

View file

@ -3,6 +3,8 @@
require_relative "../../support/sentiment_inference_stubs"
RSpec.describe Jobs::SentimentBackfill do
subject(:job) { described_class.new }
before { enable_current_plugin }
describe "#execute" do
@ -19,7 +21,7 @@ RSpec.describe Jobs::SentimentBackfill do
it "backfills when settings are correct" do
SentimentInferenceStubs.stub_classification(post)
subject.execute({})
job.execute({})
expect(ClassificationResult.where(target: post).count).to eq(expected_analysis)
end
@ -27,7 +29,7 @@ RSpec.describe Jobs::SentimentBackfill do
it "does nothing when batch size is zero" do
SiteSetting.ai_sentiment_backfill_maximum_posts_per_hour = 0
subject.execute({})
job.execute({})
expect(ClassificationResult.count).to be_zero
end
@ -35,7 +37,7 @@ RSpec.describe Jobs::SentimentBackfill do
it "does nothing when sentiment is disabled" do
SiteSetting.ai_sentiment_enabled = false
subject.execute({})
job.execute({})
expect(ClassificationResult.count).to be_zero
end
@ -45,7 +47,7 @@ RSpec.describe Jobs::SentimentBackfill do
SiteSetting.ai_sentiment_backfill_post_max_age_days = 80
post_2 = Fabricate(:post, created_at: 81.days.ago)
subject.execute({})
job.execute({})
expect(ClassificationResult.where(target: post).count).to eq(expected_analysis)
expect(ClassificationResult.where(target: post_2).count).to be_zero

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::SummariesBackfill do
subject(:job) { described_class.new }
fab!(:topic) do
Fabricate(:topic, word_count: 200, highest_post_number: 2, last_posted_at: 2.hours.ago)
end
@ -20,19 +22,19 @@ RSpec.describe Jobs::SummariesBackfill do
context "when no summary has been backfilled yet" do
it "returns the full budget" do
expect(subject.current_budget(type)).to eq(limit / intervals)
expect(job.current_budget(type)).to eq(limit / intervals)
end
it "ignores summaries generated by users" do
Fabricate(:ai_summary, target: topic, origin: AiSummary.origins[:human])
expect(subject.current_budget(type)).to eq(limit / intervals)
expect(job.current_budget(type)).to eq(limit / intervals)
end
it "only accounts for summaries of the given type" do
Fabricate(:topic_ai_gist, target: topic, origin: AiSummary.origins[:human])
expect(subject.current_budget(type)).to eq(limit / intervals)
expect(job.current_budget(type)).to eq(limit / intervals)
end
end
end
@ -43,26 +45,26 @@ RSpec.describe Jobs::SummariesBackfill do
it "only selects posts with enough words" do
topic.update!(word_count: 100)
expect(subject.backfill_candidates(type)).to be_empty
expect(job.backfill_candidates(type)).to be_empty
end
it "ignores up to date summaries" do
Fabricate(:ai_summary, target: topic, highest_target_number: 2, updated_at: 10.minutes.ago)
expect(subject.backfill_candidates(type)).to be_empty
expect(job.backfill_candidates(type)).to be_empty
end
it "ignores outdated summaries updated less than five minutes ago" do
Fabricate(:ai_summary, target: topic, highest_target_number: 1, updated_at: 4.minutes.ago)
expect(subject.backfill_candidates(type)).to be_empty
expect(job.backfill_candidates(type)).to be_empty
end
it "orders candidates by topic#last_posted_at" do
topic.update!(last_posted_at: 1.minute.ago)
topic_2 = Fabricate(:topic, word_count: 200, last_posted_at: 2.minutes.ago)
expect(subject.backfill_candidates(type).map(&:id)).to contain_exactly(topic.id, topic_2.id)
expect(job.backfill_candidates(type).map(&:id)).to contain_exactly(topic.id, topic_2.id)
end
it "prioritizes topics without summaries" do
@ -71,14 +73,14 @@ RSpec.describe Jobs::SummariesBackfill do
topic.update!(last_posted_at: 1.minute.ago)
Fabricate(:ai_summary, target: topic, updated_at: 1.hour.ago, highest_target_number: 1)
expect(subject.backfill_candidates(type).map(&:id)).to contain_exactly(topic_2.id, topic.id)
expect(job.backfill_candidates(type).map(&:id)).to contain_exactly(topic_2.id, topic.id)
end
it "respects max age setting" do
SiteSetting.ai_summary_backfill_topic_max_age_days = 1
topic.update!(last_posted_at: 2.days.ago)
expect(subject.backfill_candidates(type)).to be_empty
expect(job.backfill_candidates(type)).to be_empty
end
end
@ -97,7 +99,7 @@ RSpec.describe Jobs::SummariesBackfill do
DiscourseAi::Completions::Llm.with_prepared_responses(
[gist_1, gist_2, summary_1, summary_2],
) { subject.execute({}) }
) { job.execute({}) }
expect(AiSummary.complete.find_by(target: topic_2).summarized_text).to eq(summary_1)
expect(AiSummary.gist.find_by(target: topic_2).summarized_text).to eq(gist_1)
@ -105,13 +107,13 @@ RSpec.describe Jobs::SummariesBackfill do
expect(AiSummary.gist.find_by(target: topic).summarized_text).to eq(gist_2)
# Queue has to be empty if we just generated all summaries
expect(subject.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
expect(subject.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty
expect(job.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
expect(job.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty
# Queue still empty when they are up to date and time passes.
AiSummary.update_all(updated_at: 20.minutes.ago)
expect(subject.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
expect(subject.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty
expect(job.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
expect(job.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty
end
it "updates the highest_target_number if the summary turned to be up to date" do
@ -126,7 +128,7 @@ RSpec.describe Jobs::SummariesBackfill do
topic.update!(highest_post_number: og_highest_post_number + 1)
# No prepared responses here. We don't perform a completion call.
subject.execute({})
job.execute({})
expect(existing_summary.reload.highest_target_number).to eq(og_highest_post_number + 1)
end

View file

@ -3,7 +3,7 @@
require "webmock/rspec"
RSpec.describe DiscourseAi::Inference::CloudflareWorkersAi do
subject { described_class.new(endpoint, api_token) }
subject(:cloudflare_workers_ai) { described_class.new(endpoint, api_token) }
let(:account_id) { "test_account_id" }
let(:api_token) { "test_api_token" }
@ -36,7 +36,7 @@ RSpec.describe DiscourseAi::Inference::CloudflareWorkersAi do
let(:response_body) { { result: { data: ["embedding_result"] } }.to_json }
it "returns the embedding result" do
result = subject.perform!(content)
result = cloudflare_workers_ai.perform!(content)
expect(result).to eq("embedding_result")
end
end
@ -47,7 +47,7 @@ RSpec.describe DiscourseAi::Inference::CloudflareWorkersAi do
it "raises a Net::HTTPBadResponse error" do
allow(Rails.logger).to receive(:warn)
expect { subject.perform!(content) }.to raise_error(Net::HTTPBadResponse)
expect { cloudflare_workers_ai.perform!(content) }.to raise_error(Net::HTTPBadResponse)
expect(Rails.logger).to have_received(:warn).with(
"Cloudflare Workers AI Embeddings failed with status: #{response_status} body: #{response_body}",
)

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe Jobs::CreateAiReply do
subject(:job) { described_class.new }
fab!(:gpt_35_bot) { Fabricate(:llm_model, name: "gpt-3.5-turbo") }
before do
@ -24,11 +26,7 @@ RSpec.describe Jobs::CreateAiReply do
bot_user = DiscourseAi::AiBot::EntryPoint.find_user_from_model("gpt-3.5-turbo")
DiscourseAi::Completions::Llm.with_prepared_responses([expected_response]) do
subject.execute(
post_id: topic.first_post.id,
bot_user_id: bot_user.id,
persona_id: persona_id,
)
job.execute(post_id: topic.first_post.id, bot_user_id: bot_user.id, persona_id: persona_id)
end
expect(topic.posts.last.raw).to eq(expected_response)

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::AiHelper::Assistant do
subject(:assistant) { described_class.new }
fab!(:user)
fab!(:empty_locale_user) { Fabricate(:user, locale: "") }
@ -19,14 +21,14 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
describe("#custom_locale_instructions") do
it "Properly generates the per locale system instruction" do
SiteSetting.default_locale = "ko"
expect(subject.custom_locale_instructions(user, false)).to eq(
expect(assistant.custom_locale_instructions(user, false)).to eq(
"It is imperative that you write your answer in Korean (한국어), you are interacting with a Korean (한국어) speaking user. Leave tag names in English.",
)
SiteSetting.allow_user_locale = true
user.update!(locale: "he")
expect(subject.custom_locale_instructions(user, false)).to eq(
expect(assistant.custom_locale_instructions(user, false)).to eq(
"It is imperative that you write your answer in Hebrew (עברית), you are interacting with a Hebrew (עברית) speaking user. Leave tag names in English.",
)
end
@ -36,7 +38,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
SiteSetting.allow_user_locale = true
user.update!(locale: "he")
expect(subject.custom_locale_instructions(user, true)).to eq(
expect(assistant.custom_locale_instructions(user, true)).to eq(
"It is imperative that you write your answer in Korean (한국어), you are interacting with a Korean (한국어) speaking user. Leave tag names in English.",
)
end
@ -49,7 +51,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
end
it "returns all available prompts" do
prompts = subject.available_prompts(user)
prompts = assistant.available_prompts(user)
expect(prompts.map { |p| p[:name] }).to contain_exactly(
"translate",
@ -62,7 +64,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
end
it "returns all prompts to be shown in the composer" do
prompts = subject.available_prompts(user)
prompts = assistant.available_prompts(user)
filtered_prompts = prompts.select { |prompt| prompt[:location].include?("composer") }
expect(filtered_prompts.map { |p| p[:name] }).to contain_exactly(
@ -75,7 +77,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
end
it "returns all prompts to be shown in the post menu" do
prompts = subject.available_prompts(user)
prompts = assistant.available_prompts(user)
filtered_prompts = prompts.select { |prompt| prompt[:location].include?("post") }
expect(filtered_prompts.map { |p| p[:name] }).to contain_exactly(
@ -87,7 +89,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
it "does not raise an error when effective_locale does not exactly match keys in LocaleSiteSetting" do
SiteSetting.default_locale = "zh_CN"
expect { subject.available_prompts(user) }.not_to raise_error
expect { assistant.available_prompts(user) }.not_to raise_error
end
context "when illustrate post model is enabled" do
@ -97,7 +99,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
end
it "returns the illustrate_post prompt in the list of all prompts" do
prompts = subject.available_prompts(user)
prompts = assistant.available_prompts(user)
expect(prompts.map { |p| p[:name] }).to contain_exactly(
"translate",
@ -118,13 +120,13 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
let(:context) { DiscourseAi::Personas::BotContext.new(user: user) }
it "is able to perform %LANGUAGE% replacements" do
subject.attach_user_context(context, user)
assistant.attach_user_context(context, user)
expect(context.user_language).to eq("English (US)")
end
it "handles users with empty string locales" do
subject.attach_user_context(context, empty_locale_user)
assistant.attach_user_context(context, empty_locale_user)
expect(context.user_language).to eq("English (US)")
end
@ -135,7 +137,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
user.user_option.update!(timezone: timezone)
freeze_time "2024-01-01 12:00:00"
subject.attach_user_context(context, user)
assistant.attach_user_context(context, user)
expect(context.temporal_context).to include(%("timezone":"America/New_York"))
end
@ -144,7 +146,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
user.user_option.update!(timezone: nil)
freeze_time "2024-01-01 12:00:00" do
subject.attach_user_context(context, user)
assistant.attach_user_context(context, user)
parsed_context = JSON.parse(context.temporal_context)
expect(parsed_context.dig("user", "timezone")).to eq("UTC")
@ -152,7 +154,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
end
it "does not replace temporal context when user is nil" do
subject.attach_user_context(context, nil)
assistant.attach_user_context(context, nil)
expect(context.temporal_context).to be_nil
end
@ -172,7 +174,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
it "Sends the prompt to the LLM and returns the response" do
response =
DiscourseAi::Completions::Llm.with_prepared_responses([english_text]) do
subject.generate_and_send_prompt(mode, text_to_translate, user)
assistant.generate_and_send_prompt(mode, text_to_translate, user)
end
expect(response[:suggestions]).to contain_exactly(english_text)
@ -185,7 +187,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
response =
DiscourseAi::Completions::Llm.with_prepared_responses([english_text]) do
subject.generate_and_send_prompt(mode, text_to_translate, user)
assistant.generate_and_send_prompt(mode, text_to_translate, user)
end
expect(response[:suggestions]).to contain_exactly(english_text)
@ -219,7 +221,7 @@ RSpec.describe DiscourseAi::AiHelper::Assistant do
response =
DiscourseAi::Completions::Llm.with_prepared_responses([titles]) do
subject.generate_and_send_prompt(mode, english_text, user)
assistant.generate_and_send_prompt(mode, english_text, user)
end
expect(response[:suggestions]).to contain_exactly(*expected)

View file

@ -41,7 +41,7 @@ RSpec.describe DiscourseAi::AiHelper::Painter do
thumbnails =
DiscourseAi::Completions::Llm.with_prepared_responses([expected_image_prompt]) do
thumbnails = subject.commission_thumbnails(raw_content, user)
thumbnails = painter.commission_thumbnails(raw_content, user)
end
thumbnail_urls = Upload.last(4).map(&:short_url)
@ -77,7 +77,7 @@ RSpec.describe DiscourseAi::AiHelper::Painter do
end
.to_return(status: 200, body: { data: data }.to_json)
thumbnails = subject.commission_thumbnails(raw_content, user)
thumbnails = painter.commission_thumbnails(raw_content, user)
thumbnail_urls = Upload.last(1).map(&:short_url)
expect(

View file

@ -1,13 +1,13 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::Embeddings::SemanticSearch do
subject(:semantic_search) { described_class.new(Guardian.new(user)) }
fab!(:post)
fab!(:user)
fab!(:vector_def) { Fabricate(:embedding_definition) }
let(:query) { "test_query" }
let(:subject) { described_class.new(Guardian.new(user)) }
fab!(:vector_def) { Fabricate(:embedding_definition) }
before do
enable_current_plugin
@ -29,7 +29,7 @@ RSpec.describe DiscourseAi::Embeddings::SemanticSearch do
def trigger_search(query)
DiscourseAi::Completions::Llm.with_prepared_responses([hypothetical_post]) do
subject.search_for_topics(query)
semantic_search.search_for_topics(query)
end
end

View file

@ -3,6 +3,8 @@
require_relative "../../../../../support/sentiment_inference_stubs"
describe Jobs::PostSentimentAnalysis do
subject(:job) { described_class.new }
before { enable_current_plugin }
describe "#execute" do
@ -18,19 +20,19 @@ describe Jobs::PostSentimentAnalysis do
it "does nothing when ai_sentiment_enabled is disabled" do
SiteSetting.ai_sentiment_enabled = false
subject.execute({ post_id: post.id })
job.execute({ post_id: post.id })
expect(ClassificationResult.where(target: post).count).to be_zero
end
it "does nothing if there's no arg called post_id" do
subject.execute({})
job.execute({})
expect(ClassificationResult.where(target: post).count).to be_zero
end
it "does nothing if no post match the given id" do
subject.execute({ post_id: nil })
job.execute({ post_id: nil })
expect(ClassificationResult.where(target: post).count).to be_zero
end
@ -38,7 +40,7 @@ describe Jobs::PostSentimentAnalysis do
it "does nothing if the post content is blank" do
post.update_columns(raw: "")
subject.execute({ post_id: post.id })
job.execute({ post_id: post.id })
expect(ClassificationResult.where(target: post).count).to be_zero
end
@ -48,7 +50,7 @@ describe Jobs::PostSentimentAnalysis do
expected_analysis = DiscourseAi::Sentiment::PostClassification.new.classifiers.length
SentimentInferenceStubs.stub_classification(post)
subject.execute({ post_id: post.id })
job.execute({ post_id: post.id })
expect(ClassificationResult.where(target: post).count).to eq(expected_analysis)
end

View file

@ -3,6 +3,8 @@
require_relative "../../../support/sentiment_inference_stubs"
RSpec.describe DiscourseAi::Sentiment::PostClassification do
subject(:post_classification) { described_class.new }
before do
enable_current_plugin
SiteSetting.ai_sentiment_enabled = true
@ -26,7 +28,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
it "does nothing if the post content is blank" do
post_1.update_columns(raw: "")
subject.classify!(post_1)
post_classification.classify!(post_1)
expect(ClassificationResult.where(target: post_1).count).to be_zero
end
@ -35,7 +37,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
expected_analysis = DiscourseAi::Sentiment::SentimentSiteSettingJsonSchema.values.length
SentimentInferenceStubs.stub_classification(post_1)
subject.classify!(post_1)
post_classification.classify!(post_1)
expect(ClassificationResult.where(target: post_1).count).to eq(expected_analysis)
end
@ -43,14 +45,14 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
it "classification results must be { emotion => score }" do
SentimentInferenceStubs.stub_classification(post_1)
subject.classify!(post_1)
post_classification.classify!(post_1)
check_classification_for(post_1)
end
it "does nothing if there are no classification model" do
SiteSetting.ai_sentiment_model_configs = ""
subject.classify!(post_1)
post_classification.classify!(post_1)
expect(ClassificationResult.where(target: post_1).count).to be_zero
end
@ -58,7 +60,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
it "don't reclassify everything when a model config changes" do
SentimentInferenceStubs.stub_classification(post_1)
subject.classify!(post_1)
post_classification.classify!(post_1)
first_classified_at = 2.days.ago
ClassificationResult.update_all(created_at: first_classified_at)
@ -67,7 +69,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
SiteSetting.ai_sentiment_model_configs = current_models.to_json
SentimentInferenceStubs.stub_classification(post_1)
subject.classify!(post_1.reload)
post_classification.classify!(post_1.reload)
new_classifications = ClassificationResult.where("created_at > ?", first_classified_at).count
expect(new_classifications).to eq(1)
@ -83,7 +85,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
SentimentInferenceStubs.stub_classification(post_1)
SentimentInferenceStubs.stub_classification(post_2)
subject.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
post_classification.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
expect(ClassificationResult.where(target: post_1).count).to eq(expected_analysis)
expect(ClassificationResult.where(target: post_2).count).to eq(expected_analysis)
@ -93,7 +95,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
SentimentInferenceStubs.stub_classification(post_1)
SentimentInferenceStubs.stub_classification(post_2)
subject.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
post_classification.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
check_classification_for(post_1)
check_classification_for(post_2)
@ -102,7 +104,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
it "does nothing if there are no classification model" do
SiteSetting.ai_sentiment_model_configs = ""
subject.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
post_classification.bulk_classify!(Post.where(id: [post_1.id, post_2.id]))
expect(ClassificationResult.where(target: post_1).count).to be_zero
expect(ClassificationResult.where(target: post_2).count).to be_zero
@ -111,7 +113,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
it "don't reclassify everything when a model config changes" do
SentimentInferenceStubs.stub_classification(post_1)
subject.bulk_classify!(Post.where(id: [post_1.id]))
post_classification.bulk_classify!(Post.where(id: [post_1.id]))
first_classified_at = 2.days.ago
ClassificationResult.update_all(created_at: first_classified_at)
@ -120,7 +122,7 @@ RSpec.describe DiscourseAi::Sentiment::PostClassification do
SiteSetting.ai_sentiment_model_configs = current_models.to_json
SentimentInferenceStubs.stub_classification(post_1)
subject.bulk_classify!(Post.where(id: [post_1.id]))
post_classification.bulk_classify!(Post.where(id: [post_1.id]))
new_classifications = ClassificationResult.where("created_at > ?", first_classified_at).count
expect(new_classifications).to eq(1)

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::Personas::Researcher do
let(:researcher) { subject }
subject(:researcher) { described_class.new }
before { enable_current_plugin }

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::Personas::SettingsExplorer do
let(:settings_explorer) { subject }
subject(:settings_explorer) { described_class.new }
before { enable_current_plugin }

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::Personas::SqlHelper do
let(:sql_helper) { subject }
subject(:sql_helper) { described_class.new }
before { enable_current_plugin }

View file

@ -4,13 +4,15 @@ RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do
before { enable_current_plugin }
describe "#invoke" do
subject { described_class.new({ options: options }, bot_user: nil, llm: nil).invoke }
subject(:random_picker) do
described_class.new({ options: options }, bot_user: nil, llm: nil).invoke
end
context "with options as simple list of strings" do
let(:options) { %w[apple banana cherry] }
it "returns one of the options" do
expect(options).to include(subject[:result])
expect(options).to include(random_picker[:result])
end
end
@ -18,7 +20,7 @@ RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do
let(:options) { %w[1-3 10-20] }
it "returns a number within one of the provided ranges" do
results = subject[:result]
results = random_picker[:result]
expect(results).to all(
satisfy { |result| (1..3).include?(result) || (10..20).include?(result) },
)
@ -29,7 +31,7 @@ RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do
let(:options) { %w[red,green,blue mon,tue,wed] }
it "returns one value from each comma-separated list" do
results = subject[:result]
results = random_picker[:result]
expect(results).to include(a_kind_of(String))
results.each { |result| expect(result.split(",")).to include(result) }
end
@ -39,7 +41,7 @@ RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do
let(:options) { %w[apple 1-3 mon,tue,wed] }
it "handles each option appropriately" do
results = subject[:result]
results = random_picker[:result]
expect(results.size).to eq(options.size)
# Verifying each type of option is respected needs a more elaborate setup,
# potentially mocking or specific expectations for each type.
@ -50,7 +52,7 @@ RSpec.describe DiscourseAi::Personas::Tools::RandomPicker do
let(:options) { ["invalid_format"] }
it "returns an error message for invalid formats" do
expect(subject[:result]).to include("invalid_format")
expect(random_picker[:result]).to include("invalid_format")
end
end
end

View file

@ -6,7 +6,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
fab!(:ai_persona)
fab!(:group)
subject { described_class.new(admin) }
subject(:ai_staff_action_logger) { described_class.new(admin) }
before { enable_current_plugin }
@ -29,7 +29,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
# Setup model with sensitive data
llm_model.update!(api_key: "secret_key")
subject.log_creation("llm_model", llm_model, field_config, entity_details)
ai_staff_action_logger.log_creation("llm_model", llm_model, field_config, entity_details)
expect(staff_action_logger).to have_received(:log_custom).with(
"create_ai_llm_model",
@ -57,7 +57,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
field_config = { name: {}, description: {}, system_prompt: { type: :large_text } }
subject.log_creation("persona", ai_persona, field_config, entity_details)
ai_staff_action_logger.log_creation("persona", ai_persona, field_config, entity_details)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(
@ -97,7 +97,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
display_name: llm_model.display_name,
}
subject.log_creation("llm_model", llm_model, field_config, entity_details)
ai_staff_action_logger.log_creation("llm_model", llm_model, field_config, entity_details)
expect(staff_action_logger).to have_received(:log_custom).with(
"create_ai_llm_model",
@ -131,7 +131,13 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
# Create entity details
entity_details = { persona_id: ai_persona.id, persona_name: ai_persona.name }
subject.log_update("persona", ai_persona, initial_attributes, field_config, entity_details)
ai_staff_action_logger.log_update(
"persona",
ai_persona,
initial_attributes,
field_config,
entity_details,
)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(
@ -173,7 +179,13 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
display_name: llm_model.display_name,
}
subject.log_update("llm_model", llm_model, initial_attributes, field_config, entity_details)
ai_staff_action_logger.log_update(
"llm_model",
llm_model,
initial_attributes,
field_config,
entity_details,
)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(
@ -208,7 +220,13 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
display_name: llm_model.display_name,
}
subject.log_update("llm_model", llm_model, initial_attributes, field_config, entity_details)
ai_staff_action_logger.log_update(
"llm_model",
llm_model,
initial_attributes,
field_config,
entity_details,
)
# Verify log_custom was not called
expect(staff_action_logger).not_to have_received(:log_custom)
@ -244,7 +262,13 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
display_name: llm_model.display_name,
}
subject.log_update("llm_model", llm_model, initial_attributes, field_config, entity_details)
ai_staff_action_logger.log_update(
"llm_model",
llm_model,
initial_attributes,
field_config,
entity_details,
)
# Provider should not appear in the logged changes
expect(staff_action_logger).to have_received(:log_custom).with(
@ -281,7 +305,13 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
# Create entity details
entity_details = { persona_id: ai_persona.id, persona_name: ai_persona.name }
subject.log_update("persona", ai_persona, initial_attributes, field_config, entity_details)
ai_staff_action_logger.log_update(
"persona",
ai_persona,
initial_attributes,
field_config,
entity_details,
)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(
@ -308,7 +338,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
name: llm_model.name,
}
subject.log_deletion("llm_model", details)
ai_staff_action_logger.log_deletion("llm_model", details)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(
@ -330,7 +360,7 @@ RSpec.describe DiscourseAi::Utils::AiStaffActionLogger do
details = { key: "value" }
subject.log_custom("custom_action_type", details)
ai_staff_action_logger.log_custom("custom_action_type", details)
# Verify with have_received
expect(staff_action_logger).to have_received(:log_custom).with(

View file

@ -4,7 +4,7 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SafetyChecker do
before { enable_current_plugin }
describe "#safe?" do
subject { described_class.new(text).safe? }
subject(:safety_checker) { described_class.new(text).safe? }
context "with safe text" do
let(:text) { "This is a simple safe text without issues." }

View file

@ -1,15 +1,15 @@
# frozen_string_literal: true
RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
subject { described_class }
subject(:simple_diff) { described_class }
before { enable_current_plugin }
describe ".apply" do
it "raises error for nil inputs" do
expect { subject.apply(nil, "search", "replace") }.to raise_error(ArgumentError)
expect { subject.apply("content", nil, "replace") }.to raise_error(ArgumentError)
expect { subject.apply("content", "search", nil) }.to raise_error(ArgumentError)
expect { simple_diff.apply(nil, "search", "replace") }.to raise_error(ArgumentError)
expect { simple_diff.apply("content", nil, "replace") }.to raise_error(ArgumentError)
expect { simple_diff.apply("content", "search", nil) }.to raise_error(ArgumentError)
end
it "prioritizes exact matches over all fuzzy matches" do
@ -27,14 +27,14 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
lin 1
TEXT
expect(subject.apply(content, search, replace).strip).to eq(expected.strip)
expect(simple_diff.apply(content, search, replace).strip).to eq(expected.strip)
end
it "raises error when no match is found" do
content = "line1\ncompletely_different\nline3"
search = "nothing_like_this"
replace = "new_line"
expect { subject.apply(content, search, replace) }.to raise_error(
expect { simple_diff.apply(content, search, replace) }.to raise_error(
DiscourseAi::Utils::DiffUtils::SimpleDiff::NoMatchError,
)
end
@ -43,7 +43,7 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
content = "line1\nline2\nmiddle\nline2\nend"
search = "line2"
replace = "new_line2"
expect(subject.apply(content, search, replace)).to eq(
expect(simple_diff.apply(content, search, replace)).to eq(
"line1\nnew_line2\nmiddle\nnew_line2\nend",
)
end
@ -52,28 +52,28 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
content = "line1\nline2\nline3"
search = "line2"
replace = "new_line2"
expect(subject.apply(content, search, replace)).to eq("line1\nnew_line2\nline3")
expect(simple_diff.apply(content, search, replace)).to eq("line1\nnew_line2\nline3")
end
it "handles multi-line replacements" do
content = "start\nline1\nline2\nend"
search = "line1\nline2"
replace = "new_line"
expect(subject.apply(content, search, replace)).to eq("start\nnew_line\nend")
expect(simple_diff.apply(content, search, replace)).to eq("start\nnew_line\nend")
end
it "is forgiving of whitespace differences" do
content = "line1\n line2\nline3"
search = "line2"
replace = "new_line2"
expect(subject.apply(content, search, replace).strip).to eq("line1\n new_line2\nline3")
expect(simple_diff.apply(content, search, replace).strip).to eq("line1\n new_line2\nline3")
end
it "is forgiving of small character differences" do
content = "line one one one\nlin2\nline three three" # Notice 'lin2' instead of 'line2'
search = "line2"
replace = "new_line2"
expect(subject.apply(content, search, replace)).to eq(
expect(simple_diff.apply(content, search, replace)).to eq(
"line one one one\nnew_line2\nline three three",
)
end
@ -82,7 +82,7 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
content = "def method\n line1\n line2\nend"
search = "line1\nline2"
replace = "new_content"
expect(subject.apply(content, search, replace)).to eq("def method\nnew_content\nend")
expect(simple_diff.apply(content, search, replace)).to eq("def method\nnew_content\nend")
end
it "handles CSS blocks in different orders" do
@ -120,14 +120,14 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
}
CSS
expect(subject.apply(content, search, replace)).to eq(expected.strip)
expect(simple_diff.apply(content, search, replace)).to eq(expected.strip)
end
it "handles partial line matches" do
content = "abc hello efg\nabc hello efg"
search = "hello"
replace = "bob"
expect(subject.apply(content, search, replace)).to eq("abc bob efg\nabc bob efg")
expect(simple_diff.apply(content, search, replace)).to eq("abc bob efg\nabc bob efg")
end
it "handles JavaScript blocks in different orders" do
@ -171,7 +171,7 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
}
JS
expect(subject.apply(content, search, replace).strip).to eq(expected.strip)
expect(simple_diff.apply(content, search, replace).strip).to eq(expected.strip)
end
it "handles missing lines in search" do
@ -199,7 +199,7 @@ RSpec.describe DiscourseAi::Utils::DiffUtils::SimpleDiff do
line2
TEXT
expect(subject.apply(original, search, replace).strip).to eq(expected.strip)
expect(simple_diff.apply(original, search, replace).strip).to eq(expected.strip)
end
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
describe ReviewableAiPost do
subject(:reviewable_ai_post) { described_class.new }
fab!(:target) { Fabricate(:post) }
before { enable_current_plugin }
@ -9,7 +11,7 @@ describe ReviewableAiPost do
let(:guardian) { Guardian.new }
let(:reviewable) do
subject.tap do |r|
reviewable_ai_post.tap do |r|
r.target = target
r.target_created_by = target.user
r.created_by = Discourse.system_user

View file

@ -70,8 +70,8 @@ describe DiscourseAi::Embeddings::EmbeddingsController do
before { RateLimiter.enable }
it "will rate limit correctly" do
stub_const(subject.class, :MAX_HYDE_SEARCHES_PER_MINUTE, 1) do
stub_const(subject.class, :MAX_SEARCHES_PER_MINUTE, 2) do
stub_const(described_class, :MAX_HYDE_SEARCHES_PER_MINUTE, 1) do
stub_const(described_class, :MAX_SEARCHES_PER_MINUTE, 2) do
query = "test #{SecureRandom.hex}"
stub_embedding(query)
get "/discourse-ai/embeddings/semantic-search.json?q=#{query}&hyde=false"