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:
parent
1cb3e2c070
commit
e7f856169a
28 changed files with 216 additions and 168 deletions
|
@ -43,7 +43,3 @@ RSpec/InstanceVariable:
|
|||
Enabled: true
|
||||
Include:
|
||||
- spec/models/**/*
|
||||
|
||||
RSpec/NamedSubject:
|
||||
Exclude:
|
||||
- plugins/discourse-ai/**/*
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}",
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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." }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue