| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | RSpec.describe DiscourseAi::Embeddings::SemanticSearch do | 
					
						
							| 
									
										
										
										
											2024-03-05 16:48:28 +01:00
										 |  |  |   fab!(:post) | 
					
						
							|  |  |  |   fab!(:user) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-13 12:41:36 -03:00
										 |  |  |   let(:query) { "test_query" } | 
					
						
							| 
									
										
										
										
											2023-12-06 16:26:43 +10:00
										 |  |  |   let(:subject) { described_class.new(Guardian.new(user)) } | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-29 16:04:25 -03:00
										 |  |  |   before { SiteSetting.ai_embeddings_semantic_search_hyde_model = "fake:fake" } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |   describe "#search_for_topics" do | 
					
						
							| 
									
										
										
										
											2023-09-05 11:08:23 -03:00
										 |  |  |     let(:hypothetical_post) { "This is an hypothetical post generated from the keyword test_query" } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     before do | 
					
						
							|  |  |  |       SiteSetting.ai_embeddings_discourse_service_api_endpoint = "http://test.com" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       hyde_embedding = [0.049382, 0.9999] | 
					
						
							|  |  |  |       EmbeddingsGenerationStubs.discourse_service( | 
					
						
							|  |  |  |         SiteSetting.ai_embeddings_model, | 
					
						
							|  |  |  |         hypothetical_post, | 
					
						
							|  |  |  |         hyde_embedding, | 
					
						
							|  |  |  |       ) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     after { described_class.clear_cache_for(query) } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |     def stub_candidate_ids(candidate_ids) | 
					
						
							| 
									
										
										
										
											2024-01-11 14:16:25 -03:00
										 |  |  |       DiscourseAi::Embeddings::VectorRepresentations::BgeLargeEn | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |         .any_instance | 
					
						
							| 
									
										
										
										
											2023-09-05 11:08:23 -03:00
										 |  |  |         .expects(:asymmetric_topics_similarity_search) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |         .returns(candidate_ids) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |     def trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-11-29 15:17:46 +11:00
										 |  |  |       DiscourseAi::Completions::Llm.with_prepared_responses(["<ai>#{hypothetical_post}</ai>"]) do | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |         subject.search_for_topics(query) | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |     it "returns the first post of a topic included in the asymmetric search results" do | 
					
						
							|  |  |  |       stub_candidate_ids([post.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |       posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       expect(posts).to contain_exactly(post) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     describe "applies different scopes to the candidates" do | 
					
						
							|  |  |  |       context "when the topic is not visible" do | 
					
						
							|  |  |  |         it "returns an empty list" do | 
					
						
							|  |  |  |           post.topic.update!(visible: false) | 
					
						
							|  |  |  |           stub_candidate_ids([post.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to be_empty | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "when the post is not public" do | 
					
						
							|  |  |  |         it "returns an empty list" do | 
					
						
							|  |  |  |           pm_post = Fabricate(:private_message_post) | 
					
						
							|  |  |  |           stub_candidate_ids([pm_post.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to be_empty | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "when the post type is not visible" do | 
					
						
							|  |  |  |         it "returns an empty list" do | 
					
						
							|  |  |  |           post.update!(post_type: Post.types[:whisper]) | 
					
						
							|  |  |  |           stub_candidate_ids([post.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to be_empty | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "when the post is not the first post in the topic" do | 
					
						
							|  |  |  |         it "returns an empty list" do | 
					
						
							|  |  |  |           reply = Fabricate(:reply) | 
					
						
							|  |  |  |           reply.topic.first_post.trash! | 
					
						
							|  |  |  |           stub_candidate_ids([reply.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to be_empty | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       context "when the post is not a candidate" do | 
					
						
							|  |  |  |         it "doesn't include it in the results" do | 
					
						
							|  |  |  |           post_2 = Fabricate(:post) | 
					
						
							|  |  |  |           stub_candidate_ids([post.topic_id]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).not_to include(post_2) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-09-06 10:00:20 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |       context "when the post belongs to a secured category" do | 
					
						
							| 
									
										
										
										
											2024-03-05 16:48:28 +01:00
										 |  |  |         fab!(:group) | 
					
						
							| 
									
										
										
										
											2023-09-06 10:00:20 -03:00
										 |  |  |         fab!(:private_category) { Fabricate(:private_category, group: group) } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         before do | 
					
						
							|  |  |  |           post.topic.update!(category: private_category) | 
					
						
							|  |  |  |           stub_candidate_ids([post.topic_id]) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         it "returns an empty list" do | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-09-06 10:00:20 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to be_empty | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         it "returns the results if the user has access to the category" do | 
					
						
							|  |  |  |           group.add(user) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |           posts = trigger_search(query) | 
					
						
							| 
									
										
										
										
											2023-09-06 10:00:20 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |           expect(posts).to contain_exactly(post) | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         context "while searching as anon" do | 
					
						
							|  |  |  |           it "returns an empty list" do | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |             posts = | 
					
						
							| 
									
										
										
										
											2023-11-29 15:17:46 +11:00
										 |  |  |               DiscourseAi::Completions::Llm.with_prepared_responses( | 
					
						
							| 
									
										
										
										
											2023-11-23 12:58:54 -03:00
										 |  |  |                 ["<ai>#{hypothetical_post}</ai>"], | 
					
						
							| 
									
										
										
										
											2023-12-06 16:26:43 +10:00
										 |  |  |               ) { described_class.new(Guardian.new(nil)).search_for_topics(query) } | 
					
						
							| 
									
										
										
										
											2023-09-06 10:00:20 -03:00
										 |  |  | 
 | 
					
						
							|  |  |  |             expect(posts).to be_empty | 
					
						
							|  |  |  |           end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  |       end | 
					
						
							| 
									
										
										
										
											2023-03-31 15:29:56 -03:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |