2019-05-27 09:52:09 -04:00
# frozen_string_literal: true
describe Search do
2022-03-21 21:23:06 -04:00
fab! ( :admin ) { Fabricate ( :admin ) }
fab! ( :topic ) { Fabricate ( :topic ) }
before do
SearchIndexer . enable
Jobs . run_immediately!
end
2019-05-27 09:52:09 -04:00
context " # ts_config " do
it " maps locales to correct Postgres dictionaries " do
expect ( Search . ts_config ) . to eq ( " english " )
expect ( Search . ts_config ( " en " ) ) . to eq ( " english " )
2021-01-20 15:32:22 -05:00
expect ( Search . ts_config ( " en_GB " ) ) . to eq ( " english " )
2019-05-27 09:52:09 -04:00
expect ( Search . ts_config ( " pt_BR " ) ) . to eq ( " portuguese " )
expect ( Search . ts_config ( " tr " ) ) . to eq ( " turkish " )
expect ( Search . ts_config ( " xx " ) ) . to eq ( " simple " )
end
end
2019-10-30 13:07:16 -04:00
context " # GroupedSearchResults.blurb_for " do
it " strips audio and video URLs from search blurb " do
cooked = << ~ RAW
link to an external page : https : / / google . com / ?u = bar
link to an audio file : https : / /somesi te . com / content / file123 . m4a
2019-10-31 09:13:24 -04:00
link to a video file : https : / /somesi te . com / content / somethingelse . MOV
2019-10-30 13:07:16 -04:00
RAW
2020-07-17 04:27:30 -04:00
result = Search :: GroupedSearchResults . blurb_for ( cooked : cooked )
2019-10-30 13:07:16 -04:00
expect ( result ) . to eq ( " link to an external page: https://google.com/?u=bar link to an audio file: #{ I18n . t ( " search.audio " ) } link to a video file: #{ I18n . t ( " search.video " ) } " )
end
2019-11-06 10:32:15 -05:00
it " strips URLs correctly when blurb is longer than limit " do
2019-10-30 13:07:16 -04:00
cooked = << ~ RAW
Here goes a test cooked with enough characters to hit the blurb limit .
Something is very interesting about this audio file .
http : / / localhost / uploads / default / original / 1 X / 90 adc0092b30c04b761541bc0322d0dce3d896e7 . m4a
RAW
2020-07-17 04:27:30 -04:00
result = Search :: GroupedSearchResults . blurb_for ( cooked : cooked )
2019-10-30 13:07:16 -04:00
expect ( result ) . to eq ( " Here goes a test cooked with enough characters to hit the blurb limit. Something is very interesting about this audio file. #{ I18n . t ( " search.audio " ) } " )
end
2019-11-06 10:32:15 -05:00
it " does not fail on bad URLs " do
cooked = << ~ RAW
invalid URL : http : error ] should not trip up blurb generation .
RAW
2020-07-17 04:27:30 -04:00
result = Search :: GroupedSearchResults . blurb_for ( cooked : cooked )
2019-11-06 10:32:15 -05:00
expect ( result ) . to eq ( " invalid URL: http:error] should not trip up blurb generation. " )
end
2019-10-30 13:07:16 -04:00
end
2020-09-02 06:24:40 -04:00
context " # execute " do
before do
SiteSetting . tagging_enabled = true
end
context " staff tags " do
fab! ( :hidden_tag ) { Fabricate ( :tag ) }
let! ( :staff_tag_group ) { Fabricate ( :tag_group , permissions : { " staff " = > 1 } , tag_names : [ hidden_tag . name ] ) }
fab! ( :topic ) { Fabricate ( :topic , tags : [ hidden_tag ] ) }
fab! ( :post ) { Fabricate ( :post , topic : topic ) }
before do
SiteSetting . tagging_enabled = true
SearchIndexer . enable
SearchIndexer . index ( hidden_tag , force : true )
SearchIndexer . index ( topic , force : true )
end
it " are visible to staff users " do
result = Search . execute ( hidden_tag . name , guardian : Guardian . new ( Fabricate ( :admin ) ) )
expect ( result . tags ) . to contain_exactly ( hidden_tag )
end
it " are hidden to regular users " do
result = Search . execute ( hidden_tag . name , guardian : Guardian . new ( Fabricate ( :user ) ) )
expect ( result . tags ) . to contain_exactly ( )
end
end
2022-03-07 16:03:10 -05:00
context " accents " do
fab! ( :post_1 ) { Fabricate ( :post , raw : " Cette ****** d'art n'est pas une œuvre " ) }
fab! ( :post_2 ) { Fabricate ( :post , raw : " Cette oeuvre d'art n'est pas une ***** " ) }
before do
SearchIndexer . enable
end
after do
SearchIndexer . disable
end
it " removes them if search_ignore_accents " do
SiteSetting . search_ignore_accents = true
[ post_1 , post_2 ] . each { | post | SearchIndexer . index ( post . topic , force : true ) }
expect ( Search . execute ( " oeuvre " ) . posts ) . to contain_exactly ( post_1 , post_2 )
expect ( Search . execute ( " œuvre " ) . posts ) . to contain_exactly ( post_1 , post_2 )
end
it " does not remove them if not search_ignore_accents " do
SiteSetting . search_ignore_accents = false
[ post_1 , post_2 ] . each { | post | SearchIndexer . index ( post . topic , force : true ) }
expect ( Search . execute ( " œuvre " ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( " oeuvre " ) . posts ) . to contain_exactly ( post_2 )
end
end
2020-09-02 06:24:40 -04:00
end
2020-09-13 21:58:28 -04:00
context " custom_eager_load " do
fab! ( :topic ) { Fabricate ( :topic ) }
fab! ( :post ) { Fabricate ( :post , topic : topic ) }
before do
SearchIndexer . enable
SearchIndexer . index ( topic , force : true )
end
it " includes custom tables " do
begin
2021-06-07 11:07:46 -04:00
SiteSetting . tagging_enabled = false
2020-09-13 21:58:28 -04:00
expect ( Search . execute ( " test " ) . posts [ 0 ] . topic . association ( :category ) . loaded? ) . to be true
expect ( Search . execute ( " test " ) . posts [ 0 ] . topic . association ( :tags ) . loaded? ) . to be false
SiteSetting . tagging_enabled = true
Search . custom_topic_eager_load ( [ :topic_users ] )
Search . custom_topic_eager_load ( ) do
[ :bookmarks ]
end
expect ( Search . execute ( " test " ) . posts [ 0 ] . topic . association ( :tags ) . loaded? ) . to be true
expect ( Search . execute ( " test " ) . posts [ 0 ] . topic . association ( :topic_users ) . loaded? ) . to be true
expect ( Search . execute ( " test " ) . posts [ 0 ] . topic . association ( :bookmarks ) . loaded? ) . to be true
ensure
SiteSetting . tagging_enabled = false
Search . instance_variable_set ( :@custom_topic_eager_loads , [ ] )
end
end
end
2021-04-27 01:52:45 -04:00
context " users " do
fab! ( :user ) { Fabricate ( :user , username : " DonaldDuck " ) }
fab! ( :user2 ) { Fabricate ( :user ) }
before do
SearchIndexer . enable
SearchIndexer . index ( user , force : true )
end
it " finds users by their names or custom fields " do
result = Search . execute ( " donaldduck " , guardian : Guardian . new ( user2 ) )
expect ( result . users ) . to contain_exactly ( user )
user_field = Fabricate ( :user_field , name : " custom field " )
UserCustomField . create! ( user : user , value : " test " , name : " user_field_ #{ user_field . id } " )
Jobs :: ReindexSearch . new . execute ( { } )
result = Search . execute ( " test " , guardian : Guardian . new ( user2 ) )
expect ( result . users ) . to be_empty
user_field . update! ( searchable : true )
Jobs :: ReindexSearch . new . execute ( { } )
result = Search . execute ( " test " , guardian : Guardian . new ( user2 ) )
expect ( result . users ) . to contain_exactly ( user )
user_field2 = Fabricate ( :user_field , name : " another custom field " , searchable : true )
UserCustomField . create! ( user : user , value : " longer test " , name : " user_field_ #{ user_field2 . id } " )
UserCustomField . create! ( user : user2 , value : " second user test " , name : " user_field_ #{ user_field2 . id } " )
SearchIndexer . index ( user , force : true )
SearchIndexer . index ( user2 , force : true )
result = Search . execute ( " test " , guardian : Guardian . new ( user2 ) )
2021-06-28 21:17:49 -04:00
expect ( result . users . find { | u | u . id == user . id } . custom_data ) . to eq ( [
2021-04-27 01:52:45 -04:00
{ name : " custom field " , value : " test " } ,
{ name : " another custom field " , value : " longer test " }
] )
2021-06-28 21:17:49 -04:00
expect ( result . users . find { | u | u . id == user2 . id } . custom_data ) . to eq ( [
2021-04-27 01:52:45 -04:00
{ name : " another custom field " , value : " second user test " }
] )
end
2021-09-06 09:59:35 -04:00
context " when using SiteSetting.enable_listing_suspended_users_on_search " do
fab! ( :suspended_user ) { Fabricate ( :user , username : 'revolver_ocelot' , suspended_at : Time . now , suspended_till : 5 . days . from_now ) }
before { SearchIndexer . index ( suspended_user , force : true ) }
it " should list suspended users to regular users if the setting is enabled " do
SiteSetting . enable_listing_suspended_users_on_search = true
result = Search . execute ( " revolver_ocelot " , guardian : Guardian . new ( user ) )
expect ( result . users ) . to contain_exactly ( suspended_user )
end
it " shouldn't list suspended users to regular users if the setting is disabled " do
SiteSetting . enable_listing_suspended_users_on_search = false
result = Search . execute ( " revolver_ocelot " , guardian : Guardian . new ( user ) )
expect ( result . users ) . to be_empty
end
it " should list suspended users to admins regardless of the setting " do
SiteSetting . enable_listing_suspended_users_on_search = false
result = Search . execute ( " revolver_ocelot " , guardian : Guardian . new ( Fabricate ( :admin ) ) )
expect ( result . users ) . to contain_exactly ( suspended_user )
end
end
2021-04-27 01:52:45 -04:00
end
2021-08-02 07:04:13 -04:00
context " categories " do
it " finds topics in sub-sub-categories " do
SiteSetting . max_category_nesting = 3
category = Fabricate ( :category_with_definition )
subcategory = Fabricate ( :category_with_definition , parent_category_id : category . id )
subsubcategory = Fabricate ( :category_with_definition , parent_category_id : subcategory . id )
topic = Fabricate ( :topic , category : subsubcategory )
post = Fabricate ( :post , topic : topic )
SearchIndexer . enable
SearchIndexer . index ( post , force : true )
expect ( Search . execute ( " test # #{ category . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # #{ category . slug } : #{ subcategory . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # #{ subcategory . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # #{ subcategory . slug } : #{ subsubcategory . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # #{ subsubcategory . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # = #{ category . slug } " ) . posts ) . to be_empty
expect ( Search . execute ( " test # = #{ category . slug } : #{ subcategory . slug } " ) . posts ) . to be_empty
expect ( Search . execute ( " test # = #{ subcategory . slug } " ) . posts ) . to be_empty
expect ( Search . execute ( " test # = #{ subcategory . slug } : #{ subsubcategory . slug } " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " test # = #{ subsubcategory . slug } " ) . posts ) . to contain_exactly ( post )
end
end
2022-03-21 21:23:06 -04:00
context 'post indexing' do
fab! ( :category ) { Fabricate ( :category_with_definition , name : 'america' ) }
fab! ( :topic ) { Fabricate ( :topic , title : 'sam saffron test topic' , category : category ) }
let! ( :post ) { Fabricate ( :post , topic : topic , raw : 'this <b>fun test</b> <img src="bla" title="my image">' ) }
let! ( :post2 ) { Fabricate ( :post , topic : topic ) }
it " should index correctly " do
search_data = post . post_search_data . search_data
expect ( search_data ) . to match ( / fun / )
expect ( search_data ) . to match ( / sam / )
expect ( search_data ) . to match ( / america / )
expect do
topic . update! ( title : " harpi is the new title " )
end . to change { post2 . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
expect ( post . post_search_data . reload . search_data ) . to match ( / harpi / )
end
it 'should update posts index when topic category changes' do
expect do
topic . update! ( category : Fabricate ( :category ) )
end . to change { post . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
. and change { post2 . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
end
it 'should update posts index when topic tags changes' do
SiteSetting . tagging_enabled = true
tag = Fabricate ( :tag )
expect do
DiscourseTagging . tag_topic_by_names ( topic , Guardian . new ( admin ) , [ tag . name ] )
topic . save!
end . to change { post . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
. and change { post2 . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
expect ( topic . tags ) . to eq ( [ tag ] )
end
end
context 'user indexing' do
before do
@user = Fabricate ( :user , username : 'fred' , name : 'bob jones' )
@indexed = @user . user_search_data . search_data
end
it " should pick up on data " do
expect ( @indexed ) . to match ( / fred / )
expect ( @indexed ) . to match ( / jone / )
end
end
context 'category indexing' do
let! ( :category ) { Fabricate ( :category_with_definition , name : 'america' ) }
let! ( :topic ) { Fabricate ( :topic , category : category ) }
let! ( :post ) { Fabricate ( :post , topic : topic ) }
let! ( :post2 ) { Fabricate ( :post , topic : topic ) }
let! ( :post3 ) { Fabricate ( :post ) }
it " should index correctly " do
expect ( category . category_search_data . search_data ) . to match ( / america / )
end
it 'should update posts index when category name changes' do
expect do
category . update! ( name : 'some new name' )
end . to change { post . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
. and change { post2 . reload . post_search_data . version } . from ( SearchIndexer :: POST_INDEX_VERSION ) . to ( SearchIndexer :: REINDEX_VERSION )
expect ( post3 . post_search_data . version ) . to eq ( SearchIndexer :: POST_INDEX_VERSION )
end
end
it 'strips zero-width characters from search terms' do
term = " \ u0063 \ u0061 \ u0070 \ u0079 \ u200b \ u200c \ u200d \ ufeff \ u0062 \ u0061 \ u0072 \ u0061 " . encode ( " UTF-8 " )
expect ( term == 'capybara' ) . to eq ( false )
search = Search . new ( term )
expect ( search . valid? ) . to eq ( true )
expect ( search . term ) . to eq ( 'capybara' )
expect ( search . clean_term ) . to eq ( 'capybara' )
end
it 'replaces curly quotes to regular quotes in search terms' do
term = '“discourse”'
expect ( term == '"discourse"' ) . to eq ( false )
search = Search . new ( term )
expect ( search . valid? ) . to eq ( true )
expect ( search . term ) . to eq ( '"discourse"' )
expect ( search . clean_term ) . to eq ( '"discourse"' )
end
it 'does not search when the search term is too small' do
search = Search . new ( 'evil' , min_search_term_length : 5 )
search . execute
expect ( search . valid? ) . to eq ( false )
expect ( search . term ) . to eq ( '' )
end
it 'needs at least one term that hits the length' do
search = Search . new ( 'a b c d' , min_search_term_length : 5 )
search . execute
expect ( search . valid? ) . to eq ( false )
expect ( search . term ) . to eq ( '' )
end
it 'searches for quoted short terms' do
search = Search . new ( '"a b c d"' , min_search_term_length : 5 )
search . execute
expect ( search . valid? ) . to eq ( true )
expect ( search . term ) . to eq ( '"a b c d"' )
end
it 'searches for short terms if one hits the length' do
search = Search . new ( 'a b c okaylength' , min_search_term_length : 5 )
search . execute
expect ( search . valid? ) . to eq ( true )
expect ( search . term ) . to eq ( 'a b c okaylength' )
end
context 'query sanitization' do
let! ( :post ) { Fabricate ( :post , raw : 'hello world' ) }
it 'escapes backslash' do
expect ( Search . execute ( 'hello\\' ) . posts ) . to contain_exactly ( post )
end
it 'escapes single quote' do
expect ( Search . execute ( " hello' " ) . posts ) . to contain_exactly ( post )
end
it 'escapes non-alphanumeric characters' do
expect ( Search . execute ( 'hello :!$);}]>@\#\"\'' ) . posts ) . to contain_exactly ( post )
end
end
it 'works when given two terms with spaces' do
expect { Search . execute ( 'evil trout' ) } . not_to raise_error
end
context 'users' do
let! ( :user ) { Fabricate ( :user ) }
let ( :result ) { Search . execute ( 'bruce' , type_filter : 'user' ) }
it 'returns a result' do
expect ( result . users . length ) . to eq ( 1 )
expect ( result . users [ 0 ] . id ) . to eq ( user . id )
end
context 'hiding user profiles' do
before { SiteSetting . hide_user_profiles_from_public = true }
it 'returns no result for anon' do
expect ( result . users . length ) . to eq ( 0 )
end
it 'returns a result for logged in users' do
result = Search . execute ( 'bruce' , type_filter : 'user' , guardian : Guardian . new ( user ) )
expect ( result . users . length ) . to eq ( 1 )
end
end
end
context 'inactive users' do
let! ( :inactive_user ) { Fabricate ( :inactive_user , active : false ) }
let ( :result ) { Search . execute ( 'bruce' ) }
it 'does not return a result' do
expect ( result . users . length ) . to eq ( 0 )
end
end
context 'staged users' do
let ( :staged ) { Fabricate ( :staged ) }
let ( :result ) { Search . execute ( staged . username ) }
it 'does not return a result' do
expect ( result . users . length ) . to eq ( 0 )
end
end
context 'private messages' do
let! ( :post ) { Fabricate ( :private_message_post ) }
let ( :topic ) { post . topic }
let! ( :reply ) do
Fabricate ( :private_message_post ,
topic : post . topic ,
raw : 'hello from mars, we just landed' ,
user : post . user
)
end
let! ( :post2 ) do
Fabricate ( :private_message_post ,
raw : 'another secret pm from mars, testing'
)
end
it 'searches correctly as an admin' do
results = Search . execute (
'mars' ,
type_filter : 'private_messages' ,
guardian : Guardian . new ( admin )
)
expect ( results . posts ) . to eq ( [ ] )
end
it " searches correctly as an admin given another user's context " do
results = Search . execute (
'mars' ,
type_filter : 'private_messages' ,
search_context : reply . user ,
guardian : Guardian . new ( admin )
)
expect ( results . posts ) . to contain_exactly ( reply )
end
it " raises the right error when a normal user searches for another user's context " do
expect do
Search . execute (
'mars' ,
search_context : reply . user ,
type_filter : 'private_messages' ,
guardian : Guardian . new ( Fabricate ( :user ) )
)
end . to raise_error ( Discourse :: InvalidAccess )
end
it 'searches correctly as a user' do
results = Search . execute (
'mars' ,
type_filter : 'private_messages' ,
guardian : Guardian . new ( reply . user )
)
expect ( results . posts ) . to contain_exactly ( reply )
end
it 'searches correctly for a user with no private messages' do
results = Search . execute (
'mars' ,
type_filter : 'private_messages' ,
guardian : Guardian . new ( Fabricate ( :user ) )
)
expect ( results . posts ) . to eq ( [ ] )
end
it 'searches correctly' do
expect do
Search . execute ( 'mars' , type_filter : 'private_messages' )
end . to raise_error ( Discourse :: InvalidAccess )
results = Search . execute (
'mars' ,
type_filter : 'private_messages' ,
guardian : Guardian . new ( reply . user )
)
expect ( results . posts ) . to contain_exactly ( reply )
results = Search . execute (
'mars' ,
search_context : topic ,
guardian : Guardian . new ( reply . user )
)
expect ( results . posts ) . to contain_exactly ( reply )
# can search group PMs as well as non admin
user = Fabricate ( :user )
group = Fabricate . build ( :group )
group . add ( user )
group . save!
TopicAllowedGroup . create! ( group_id : group . id , topic_id : topic . id )
[ " mars in:personal " , " mars IN:PERSONAL " ] . each do | query |
results = Search . execute ( query , guardian : Guardian . new ( user ) )
expect ( results . posts ) . to contain_exactly ( reply )
end
end
context 'personal_messages filter' do
it 'does not allow a normal user to search for personal messages of another user' do
expect do
Search . execute (
" mars personal_messages: #{ post . user . username } " ,
guardian : Guardian . new ( Fabricate ( :user ) )
)
end . to raise_error ( Discourse :: InvalidAccess )
end
it 'searches correctly for the PM of the given user' do
results = Search . execute (
" mars personal_messages: #{ post . user . username } " ,
guardian : Guardian . new ( admin )
)
expect ( results . posts ) . to contain_exactly ( reply )
end
it 'returns the right results if username is invalid' do
results = Search . execute (
" mars personal_messages:random_username " ,
guardian : Guardian . new ( admin )
)
expect ( results . posts ) . to eq ( [ ] )
end
end
context 'all-pms flag' do
it 'returns matching PMs if the user is an admin' do
results = Search . execute ( 'mars in:all-pms' , guardian : Guardian . new ( admin ) )
expect ( results . posts ) . to include ( reply , post2 )
end
it 'returns nothing if the user is not an admin' do
results = Search . execute ( 'mars in:all-pms' , guardian : Guardian . new ( Fabricate ( :user ) ) )
expect ( results . posts ) . to be_empty
end
it 'returns nothing if the user is a moderator' do
results = Search . execute ( 'mars in:all-pms' , guardian : Guardian . new ( Fabricate ( :moderator ) ) )
expect ( results . posts ) . to be_empty
end
end
context 'personal-direct flag' do
let ( :current ) { Fabricate ( :user , admin : true , username : " current_user " ) }
let ( :participant ) { Fabricate ( :user , username : " participant_1 " ) }
let ( :participant_2 ) { Fabricate ( :user , username : " participant_2 " ) }
let ( :group ) do
group = Fabricate ( :group , has_messages : true )
group . add ( current )
group . add ( participant )
group
end
def create_pm ( users : , group : nil )
pm = Fabricate ( :private_message_post_one_user , user : users . first ) . topic
users [ 1 .. - 1 ] . each do | u |
pm . invite ( users . first , u . username )
Fabricate ( :post , user : u , topic : pm )
end
if group
pm . invite_group ( users . first , group )
group . users . each do | u |
Fabricate ( :post , user : u , topic : pm )
end
end
pm . reload
end
it 'can find all direct PMs of the current user' do
pm = create_pm ( users : [ current , participant ] )
_pm_2 = create_pm ( users : [ participant_2 , participant ] )
pm_3 = create_pm ( users : [ participant , current ] )
pm_4 = create_pm ( users : [ participant_2 , current ] )
[ " in:personal-direct " , " In:PeRsOnAl-DiReCt " ] . each do | query |
results = Search . execute ( query , guardian : Guardian . new ( current ) )
expect ( results . posts . size ) . to eq ( 3 )
expect ( results . posts . map ( & :topic_id ) ) . to eq ( [ pm_4 . id , pm_3 . id , pm . id ] )
end
end
it 'can filter direct PMs by @username' do
pm = create_pm ( users : [ current , participant ] )
pm_2 = create_pm ( users : [ participant , current ] )
pm_3 = create_pm ( users : [ participant_2 , current ] )
[
" @ #{ participant . username } in:personal-direct " ,
" @ #{ participant . username } iN:pErSoNaL-dIrEcT " ,
] . each do | query |
results = Search . execute ( query , guardian : Guardian . new ( current ) )
expect ( results . posts . size ) . to eq ( 2 )
expect ( results . posts . map ( & :topic_id ) ) . to contain_exactly ( pm_2 . id , pm . id )
expect ( results . posts . map ( & :user_id ) . uniq ) . to eq ( [ participant . id ] )
end
results = Search . execute ( " @me in:personal-direct " , guardian : Guardian . new ( current ) )
expect ( results . posts . size ) . to eq ( 3 )
expect ( results . posts . map ( & :topic_id ) ) . to contain_exactly ( pm_3 . id , pm_2 . id , pm . id )
expect ( results . posts . map ( & :user_id ) . uniq ) . to eq ( [ current . id ] )
end
it " doesn't include PMs that have more than 2 participants " do
_pm = create_pm ( users : [ current , participant , participant_2 ] )
results = Search . execute ( " @ #{ participant . username } in:personal-direct " , guardian : Guardian . new ( current ) )
expect ( results . posts . size ) . to eq ( 0 )
end
it " doesn't include PMs that have groups " do
_pm = create_pm ( users : [ current , participant ] , group : group )
results = Search . execute ( " @ #{ participant . username } in:personal-direct " , guardian : Guardian . new ( current ) )
expect ( results . posts . size ) . to eq ( 0 )
end
end
context 'all topics' do
let! ( :u1 ) { Fabricate ( :user , username : 'fred' , name : 'bob jones' , email : 'foo+1@bar.baz' ) }
let! ( :u2 ) { Fabricate ( :user , username : 'bob' , name : 'fred jones' , email : 'foo+2@bar.baz' ) }
let! ( :u3 ) { Fabricate ( :user , username : 'jones' , name : 'bob fred' , email : 'foo+3@bar.baz' ) }
let! ( :u4 ) { Fabricate ( :user , username : 'alice' , name : 'bob fred' , email : 'foo+4@bar.baz' , admin : true ) }
let! ( :public_topic ) { Fabricate ( :topic , user : u1 ) }
let! ( :public_post1 ) { Fabricate ( :post , topic : public_topic , raw : " what do you want for breakfast? ham and eggs? " , user : u1 ) }
let! ( :public_post2 ) { Fabricate ( :post , topic : public_topic , raw : " ham and spam " , user : u2 ) }
let! ( :private_topic ) { Fabricate ( :topic , user : u1 , category_id : nil , archetype : 'private_message' ) }
let! ( :private_post1 ) { Fabricate ( :post , topic : private_topic , raw : " what do you want for lunch? ham and cheese? " , user : u1 ) }
let! ( :private_post2 ) { Fabricate ( :post , topic : private_topic , raw : " cheese and spam " , user : u2 ) }
it 'finds private messages' do
TopicAllowedUser . create! ( user_id : u1 . id , topic_id : private_topic . id )
TopicAllowedUser . create! ( user_id : u2 . id , topic_id : private_topic . id )
# case insensitive only
results = Search . execute ( 'iN:aLL cheese' , guardian : Guardian . new ( u1 ) )
expect ( results . posts ) . to contain_exactly ( private_post1 )
# private only
results = Search . execute ( 'in:all cheese' , guardian : Guardian . new ( u1 ) )
expect ( results . posts ) . to contain_exactly ( private_post1 )
# public only
results = Search . execute ( 'in:all eggs' , guardian : Guardian . new ( u1 ) )
expect ( results . posts ) . to contain_exactly ( public_post1 )
# both
results = Search . execute ( 'in:all spam' , guardian : Guardian . new ( u1 ) )
expect ( results . posts ) . to contain_exactly ( public_post2 , private_post2 )
# for anon
results = Search . execute ( 'in:all spam' , guardian : Guardian . new )
expect ( results . posts ) . to contain_exactly ( public_post2 )
# nonparticipatory user
results = Search . execute ( 'in:all cheese' , guardian : Guardian . new ( u3 ) )
expect ( results . posts . empty? ) . to eq ( true )
results = Search . execute ( 'in:all eggs' , guardian : Guardian . new ( u3 ) )
expect ( results . posts ) . to contain_exactly ( public_post1 )
results = Search . execute ( 'in:all spam' , guardian : Guardian . new ( u3 ) )
expect ( results . posts ) . to contain_exactly ( public_post2 )
# Admin doesn't see private topic
results = Search . execute ( 'in:all spam' , guardian : Guardian . new ( u4 ) )
expect ( results . posts ) . to contain_exactly ( public_post2 )
# same keyword for different users
results = Search . execute ( 'in:all ham' , guardian : Guardian . new ( u1 ) )
expect ( results . posts ) . to contain_exactly ( public_post1 , private_post1 )
results = Search . execute ( 'in:all ham' , guardian : Guardian . new ( u2 ) )
expect ( results . posts ) . to contain_exactly ( public_post1 , private_post1 )
results = Search . execute ( 'in:all ham' , guardian : Guardian . new ( u3 ) )
expect ( results . posts ) . to contain_exactly ( public_post1 )
end
end
end
context 'posts' do
fab! ( :post ) do
SearchIndexer . enable
Fabricate ( :post )
end
let ( :topic ) { post . topic }
let! ( :reply ) do
Fabricate ( :post_with_long_raw_content ,
topic : topic ,
user : topic . user ,
) . tap { | post | post . update! ( raw : " #{ post . raw } elephant " ) }
end
let ( :expected_blurb ) do
" #{ Search :: GroupedSearchResults :: OMISSION } hundred characters to satisfy any test conditions that require content longer than the typical test post raw content. It really is some long content, folks. <span class= \" #{ Search :: HIGHLIGHT_CSS_CLASS } \" >elephant</span> "
end
it 'returns the post' do
SiteSetting . use_pg_headlines_for_excerpt = true
result = Search . execute ( 'elephant' ,
type_filter : 'topic' ,
include_blurbs : true
)
expect ( result . posts . map ( & :id ) ) . to contain_exactly ( reply . id )
post = result . posts . first
expect ( result . blurb ( post ) ) . to eq ( expected_blurb )
expect ( post . topic_title_headline ) . to eq ( topic . fancy_title )
end
it " only applies highlighting to the first #{ Search :: MAX_LENGTH_FOR_HEADLINE } characters " do
SiteSetting . use_pg_headlines_for_excerpt = true
reply . update! ( raw : " #{ 'a' * Search :: MAX_LENGTH_FOR_HEADLINE } #{ reply . raw } " )
result = Search . execute ( 'elephant' )
expect ( result . posts . map ( & :id ) ) . to contain_exactly ( reply . id )
post = result . posts . first
expect ( post . headline . include? ( 'elephant' ) ) . to eq ( false )
end
it " does not truncate topic title when applying highlights " do
SiteSetting . use_pg_headlines_for_excerpt = true
topic = reply . topic
topic . update! ( title : " #{ 'very ' * 7 } long topic title with our search term in the middle of the title " )
result = Search . execute ( 'search term' )
expect ( result . posts . first . topic_title_headline ) . to eq ( << ~ HTML . chomp )
Very very very very very very very long topic title with our < span class = \ " #{ Search :: HIGHLIGHT_CSS_CLASS } \" >search</span> <span class= \" #{ Search :: HIGHLIGHT_CSS_CLASS } \" >term</span> in the middle of the title
HTML
end
it " limits the search headline to #{ Search :: GroupedSearchResults :: BLURB_LENGTH } characters " do
SiteSetting . use_pg_headlines_for_excerpt = true
reply . update! ( raw : " #{ 'a' * Search :: GroupedSearchResults :: BLURB_LENGTH } elephant " )
result = Search . execute ( 'elephant' )
expect ( result . posts . map ( & :id ) ) . to contain_exactly ( reply . id )
post = result . posts . first
expect ( result . blurb ( post ) ) . to eq ( " #{ 'a' * Search :: GroupedSearchResults :: BLURB_LENGTH } #{ Search :: GroupedSearchResults :: OMISSION } " )
end
it 'returns the right post and blurb for searches with phrase' do
SiteSetting . use_pg_headlines_for_excerpt = true
result = Search . execute ( '"elephant"' ,
type_filter : 'topic' ,
include_blurbs : true
)
expect ( result . posts . map ( & :id ) ) . to contain_exactly ( reply . id )
expect ( result . blurb ( result . posts . first ) ) . to eq ( expected_blurb )
end
it 'applies a small penalty to closed topic when ranking' do
post = Fabricate ( :post ,
raw : " My weekly update " ,
topic : Fabricate ( :topic ,
title : " A topic that will be closed " ,
closed : true
)
)
post2 = Fabricate ( :post ,
raw : " My weekly update " ,
topic : Fabricate ( :topic ,
title : " A topic that will be open "
)
)
result = Search . execute ( 'weekly update' )
expect ( result . posts . pluck ( :id ) ) . to eq ( [ post2 . id , post . id ] )
end
it 'aggregates searches in a topic by returning the post with the lowest post number' do
post = Fabricate ( :post , topic : topic , raw : " this is a play post " )
post2 = Fabricate ( :post , topic : topic , raw : " play play playing played play " )
post3 = Fabricate ( :post , raw : " this is a play post " )
5 . times do
Fabricate ( :post , topic : topic , raw : " play playing played " )
end
results = Search . execute ( 'play' )
expect ( results . posts . map ( & :id ) ) . to eq ( [
post . id ,
post3 . id
] )
end
it " is able to search with an offset when configured " do
post_1 = Fabricate ( :post , raw : " this is a play post " )
SiteSetting . search_recent_regular_posts_offset_post_id = post_1 . id + 1
results = Search . execute ( 'play post' )
expect ( results . posts ) . to eq ( [ post_1 ] )
post_2 = Fabricate ( :post , raw : " this is another play post " )
SiteSetting . search_recent_regular_posts_offset_post_id = post_2 . id
results = Search . execute ( 'play post' )
expect ( results . posts . map ( & :id ) ) . to eq ( [
post_2 . id ,
post_1 . id
] )
end
it 'allows staff to search for whispers' do
post . update! ( post_type : Post . types [ :whisper ] , raw : 'this is a tiger' )
results = Search . execute ( 'tiger' )
expect ( results . posts ) . to eq ( [ ] )
results = Search . execute ( 'tiger' , guardian : Guardian . new ( admin ) )
expect ( results . posts ) . to eq ( [ post ] )
end
end
context 'topics' do
let ( :post ) { Fabricate ( :post ) }
let ( :topic ) { post . topic }
context 'search within topic' do
def new_post ( raw , topic = nil , created_at : nil )
topic || = Fabricate ( :topic )
Fabricate ( :post , topic : topic , topic_id : topic . id , user : topic . user , raw : raw , created_at : created_at )
end
it 'works in Chinese' do
SiteSetting . search_tokenize_chinese_japanese_korean = true
post = new_post ( 'I am not in English 何点になると思いますか' )
results = Search . execute ( '何点になると思' , search_context : post . topic )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'displays multiple results within a topic' do
topic2 = Fabricate ( :topic )
new_post ( 'this is the other post I am posting' , topic2 , created_at : 6 . minutes . ago )
new_post ( 'this is my fifth post I am posting' , topic2 , created_at : 5 . minutes . ago )
post1 = new_post ( 'this is the other post I am posting' , topic , created_at : 4 . minutes . ago )
post2 = new_post ( 'this is my first post I am posting' , topic , created_at : 3 . minutes . ago )
post3 = new_post ( 'this is a real long and complicated bla this is my second post I am Posting birds with more stuff bla bla' , topic , created_at : 2 . minutes . ago )
post4 = new_post ( 'this is my fourth post I am posting' , topic , created_at : 1 . minute . ago )
# update posts_count
topic . reload
results = Search . execute ( 'posting' , search_context : post1 . topic )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post1 . id , post2 . id , post3 . id , post4 . id ] )
results = Search . execute ( 'posting l' , search_context : post1 . topic )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post4 . id , post3 . id , post2 . id , post1 . id ] )
# stop words should work
results = Search . execute ( 'this' , search_context : post1 . topic )
expect ( results . posts . length ) . to eq ( 4 )
# phrase search works as expected
results = Search . execute ( '"fourth post I am posting"' , search_context : post1 . topic )
expect ( results . posts . length ) . to eq ( 1 )
end
it " works for unlisted topics " do
topic . update ( visible : false )
_post = new_post ( 'discourse is awesome' , topic )
results = Search . execute ( 'discourse' , search_context : topic )
expect ( results . posts . length ) . to eq ( 1 )
end
end
context 'searching the OP' do
let! ( :post ) { Fabricate ( :post_with_long_raw_content ) }
let ( :result ) { Search . execute ( 'hundred' , type_filter : 'topic' ) }
it 'returns a result correctly' do
expect ( result . posts . length ) . to eq ( 1 )
expect ( result . posts [ 0 ] . id ) . to eq ( post . id )
end
end
context 'searching for quoted title' do
it " can find quoted title " do
create_post ( raw : " this is the raw body " , title : " I am a title yeah " )
result = Search . execute ( '"a title yeah"' )
expect ( result . posts . length ) . to eq ( 1 )
end
end
context " search for a topic by id " do
let ( :result ) { Search . execute ( topic . id , type_filter : 'topic' , search_for_id : true , min_search_term_length : 1 ) }
it 'returns the topic' do
expect ( result . posts . length ) . to eq ( 1 )
expect ( result . posts . first . id ) . to eq ( post . id )
end
end
context " search for a topic by url " do
it 'returns the topic' do
result = Search . execute ( topic . relative_url , search_for_id : true , type_filter : 'topic' )
expect ( result . posts . length ) . to eq ( 1 )
expect ( result . posts . first . id ) . to eq ( post . id )
end
context 'restrict_to_archetype' do
let ( :personal_message ) { Fabricate ( :private_message_topic ) }
let! ( :p1 ) { Fabricate ( :post , topic : personal_message , post_number : 1 ) }
it 'restricts result to topics' do
result = Search . execute ( personal_message . relative_url , search_for_id : true , type_filter : 'topic' , restrict_to_archetype : Archetype . default )
expect ( result . posts . length ) . to eq ( 0 )
result = Search . execute ( topic . relative_url , search_for_id : true , type_filter : 'topic' , restrict_to_archetype : Archetype . default )
expect ( result . posts . length ) . to eq ( 1 )
end
it 'restricts result to messages' do
result = Search . execute ( topic . relative_url , search_for_id : true , type_filter : 'private_messages' , guardian : Guardian . new ( admin ) , restrict_to_archetype : Archetype . private_message )
expect ( result . posts . length ) . to eq ( 0 )
result = Search . execute ( personal_message . relative_url , search_for_id : true , type_filter : 'private_messages' , guardian : Guardian . new ( admin ) , restrict_to_archetype : Archetype . private_message )
expect ( result . posts . length ) . to eq ( 1 )
end
end
end
context 'security' do
def result ( current_user )
Search . execute ( 'hello' , guardian : Guardian . new ( current_user ) )
end
it 'secures results correctly' do
category = Fabricate ( :category_with_definition )
topic . category_id = category . id
topic . save
category . set_permissions ( staff : :full )
category . save
expect ( result ( nil ) . posts ) . not_to be_present
expect ( result ( Fabricate ( :user ) ) . posts ) . not_to be_present
expect ( result ( admin ) . posts ) . to be_present
end
end
end
context 'cyrillic topic' do
let! ( :cyrillic_topic ) do
Fabricate ( :topic ) do
user
title { sequence ( :title ) { | i | " Тестовая запись #{ i } " } }
end
end
let! ( :post ) { Fabricate ( :post , topic : cyrillic_topic , user : cyrillic_topic . user ) }
let ( :result ) { Search . execute ( 'запись' ) }
it 'finds something when given cyrillic query' do
expect ( result . posts ) . to contain_exactly ( post )
end
end
it 'does not tokenize search term' do
Fabricate ( :post , raw : 'thing is canned should still be found!' )
expect ( Search . execute ( 'canned' ) . posts ) . to be_present
end
context 'categories' do
let ( :category ) { Fabricate ( :category_with_definition , name : " monkey Category 2 " ) }
let ( :topic ) { Fabricate ( :topic , category : category ) }
let! ( :post ) { Fabricate ( :post , topic : topic , raw : " snow monkey " ) }
let! ( :ignored_category ) do
Fabricate ( :category_with_definition ,
name : " monkey Category 1 " ,
slug : " test " ,
search_priority : Searchable :: PRIORITIES [ :ignore ]
)
end
it " should return the right categories " do
search = Search . execute ( " monkey " )
expect ( search . categories ) . to contain_exactly (
category , ignored_category
)
expect ( search . posts ) . to eq ( [ category . topic . first_post , post ] )
search = Search . execute ( " monkey # test " )
expect ( search . posts ) . to eq ( [ ignored_category . topic . first_post ] )
end
describe " with child categories " do
let! ( :child_of_ignored_category ) do
Fabricate ( :category_with_definition ,
name : " monkey Category 3 " ,
parent_category : ignored_category
)
end
let! ( :post2 ) do
Fabricate ( :post ,
topic : Fabricate ( :topic , category : child_of_ignored_category ) ,
raw : " snow monkey park "
)
end
it 'returns the right results' do
search = Search . execute ( " monkey " )
expect ( search . categories ) . to contain_exactly (
category , ignored_category , child_of_ignored_category
)
expect ( search . posts . map ( & :id ) ) . to eq ( [
child_of_ignored_category . topic . first_post ,
category . topic . first_post ,
post2 ,
post
] . map ( & :id ) )
search = Search . execute ( " snow " )
expect ( search . posts . map ( & :id ) ) . to eq ( [ post2 . id , post . id ] )
category . set_permissions ( { } )
category . save!
search = Search . execute ( " monkey " )
expect ( search . categories ) . to contain_exactly (
ignored_category , child_of_ignored_category
)
expect ( search . posts . map ( & :id ) ) . to eq ( [
child_of_ignored_category . topic . first_post ,
post2
] . map ( & :id ) )
end
end
describe 'categories with different priorities' do
let ( :category2 ) { Fabricate ( :category_with_definition ) }
it " should return posts in the right order " do
raw = " The pure genuine evian "
post = Fabricate ( :post , topic : category . topic , raw : raw )
post2 = Fabricate ( :post , topic : category2 . topic , raw : raw )
post2 . topic . update! ( bumped_at : 10 . seconds . from_now )
search = Search . execute ( raw )
expect ( search . posts . map ( & :id ) ) . to eq ( [ post2 . id , post . id ] )
category . update! ( search_priority : Searchable :: PRIORITIES [ :high ] )
search = Search . execute ( raw )
expect ( search . posts . map ( & :id ) ) . to eq ( [ post . id , post2 . id ] )
end
end
end
context 'groups' do
def search ( user = Fabricate ( :user ) )
Search . execute ( group . name , guardian : Guardian . new ( user ) )
end
let! ( :group ) { Group [ :trust_level_0 ] }
it 'shows group' do
expect ( search . groups . map ( & :name ) ) . to eq ( [ group . name ] )
end
context 'group visibility' do
let! ( :group ) { Fabricate ( :group ) }
before do
group . update! ( visibility_level : 3 )
end
context 'staff logged in' do
it 'shows group' do
expect ( search ( admin ) . groups . map ( & :name ) ) . to eq ( [ group . name ] )
end
end
context 'non staff logged in' do
it 'shows doesn’ t show group' do
expect ( search . groups . map ( & :name ) ) . to be_empty
end
end
end
end
context 'tags' do
def search
Search . execute ( tag . name )
end
let! ( :tag ) { Fabricate ( :tag ) }
let! ( :uppercase_tag ) { Fabricate ( :tag , name : " HeLlO " ) }
let ( :tag_group ) { Fabricate ( :tag_group ) }
let ( :category ) { Fabricate ( :category_with_definition ) }
context 'post searching' do
before do
SiteSetting . tagging_enabled = true
DiscourseTagging . tag_topic_by_names ( post . topic , Guardian . new ( Fabricate . build ( :admin ) ) , [ tag . name , uppercase_tag . name ] )
post . topic . save
end
let ( :post ) { Fabricate ( :post , raw : 'I am special post' ) }
it 'can find posts with tags' do
# we got to make this index (it is deferred)
Jobs :: ReindexSearch . new . rebuild_posts
result = Search . execute ( tag . name )
expect ( result . posts . length ) . to eq ( 1 )
result = Search . execute ( " hElLo " )
expect ( result . posts . length ) . to eq ( 1 )
SiteSetting . tagging_enabled = false
result = Search . execute ( tag . name )
expect ( result . posts . length ) . to eq ( 0 )
end
it 'can find posts with tag synonyms' do
synonym = Fabricate ( :tag , name : 'synonym' , target_tag : tag )
Jobs :: ReindexSearch . new . rebuild_posts
result = Search . execute ( synonym . name )
expect ( result . posts . length ) . to eq ( 1 )
end
end
context 'tagging is disabled' do
before { SiteSetting . tagging_enabled = false }
it 'does not include tags' do
expect ( search . tags ) . to_not be_present
end
end
context 'tagging is enabled' do
before { SiteSetting . tagging_enabled = true }
it 'returns the tag in the result' do
expect ( search . tags ) . to eq ( [ tag ] )
end
it 'shows staff tags' do
create_staff_only_tags ( [ " #{ tag . name } 9 " ] )
expect ( Search . execute ( tag . name , guardian : Guardian . new ( admin ) ) . tags . map ( & :name ) ) . to eq ( [ tag . name , " #{ tag . name } 9 " ] )
expect ( search . tags . map ( & :name ) ) . to eq ( [ tag . name , " #{ tag . name } 9 " ] )
end
it 'includes category-restricted tags' do
category_tag = Fabricate ( :tag , name : " #{ tag . name } 9 " )
tag_group . tags = [ category_tag ]
category . set_permissions ( admins : :full )
category . allowed_tag_groups = [ tag_group . name ]
category . save!
expect ( Search . execute ( tag . name , guardian : Guardian . new ( admin ) ) . tags ) . to eq ( [ tag , category_tag ] )
expect ( search . tags ) . to eq ( [ tag , category_tag ] )
end
end
end
context 'type_filter' do
let! ( :user ) { Fabricate ( :user , username : 'amazing' , email : 'amazing@amazing.com' ) }
let! ( :category ) { Fabricate ( :category_with_definition , name : 'amazing category' , user : user ) }
context 'user filter' do
let ( :results ) { Search . execute ( 'amazing' , type_filter : 'user' ) }
it " returns a user result " do
expect ( results . categories . length ) . to eq ( 0 )
expect ( results . posts . length ) . to eq ( 0 )
expect ( results . users . length ) . to eq ( 1 )
end
end
context 'category filter' do
let ( :results ) { Search . execute ( 'amazing' , type_filter : 'category' ) }
it " returns a category result " do
expect ( results . categories . length ) . to eq ( 1 )
expect ( results . posts . length ) . to eq ( 0 )
expect ( results . users . length ) . to eq ( 0 )
end
end
end
context 'search_context' do
it 'can find a user when using search context' do
coding_horror = Fabricate ( :coding_horror )
post = Fabricate ( :post )
Fabricate ( :post , user : coding_horror )
result = Search . execute ( 'hello' , search_context : post . user )
result . posts . first . topic_id = post . topic_id
expect ( result . posts . length ) . to eq ( 1 )
end
it 'can use category as a search context' do
category = Fabricate ( :category_with_definition ,
search_priority : Searchable :: PRIORITIES [ :ignore ]
)
topic = Fabricate ( :topic , category : category )
topic_no_cat = Fabricate ( :topic )
# includes subcategory in search
subcategory = Fabricate ( :category_with_definition , parent_category_id : category . id )
sub_topic = Fabricate ( :topic , category : subcategory )
post = Fabricate ( :post , topic : topic , user : topic . user )
Fabricate ( :post , topic : topic_no_cat , user : topic . user )
sub_post = Fabricate ( :post , raw : 'I am saying hello from a subcategory' , topic : sub_topic , user : topic . user )
search = Search . execute ( 'hello' , search_context : category )
expect ( search . posts . map ( & :id ) ) . to match_array ( [ post . id , sub_post . id ] )
expect ( search . posts . length ) . to eq ( 2 )
end
it 'can use tag as a search context' do
tag = Fabricate ( :tag , name : 'important-stuff' )
topic_no_tag = Fabricate ( :topic )
Fabricate ( :topic_tag , tag : tag , topic : topic )
post = Fabricate ( :post , topic : topic , user : topic . user , raw : 'This is my hello' )
Fabricate ( :post , topic : topic_no_tag , user : topic . user )
search = Search . execute ( 'hello' , search_context : tag )
expect ( search . posts . map ( & :id ) ) . to contain_exactly ( post . id )
expect ( search . posts . length ) . to eq ( 1 )
end
end
context 'Japanese search' do
let! ( :topic ) { Fabricate ( :topic ) }
let! ( :post ) { Fabricate ( :post , topic : topic , raw : 'This is some japanese text 日本が大好きです。' ) }
let! ( :topic_2 ) { Fabricate ( :topic , title : '日本の話題、 more japanese text' ) }
let! ( :post_2 ) { Fabricate ( :post , topic : topic_2 ) }
describe '.prepare_data' do
it 'removes punctuations' do
SiteSetting . search_tokenize_japanese = true
expect ( Search . prepare_data ( post . raw ) ) . to eq ( " This is some japanese text 日本 が 大好き です " )
end
end
describe '.execute' do
before do
@old_default = SiteSetting . defaults . get ( :min_search_term_length )
SiteSetting . defaults . set_regardless_of_locale ( :min_search_term_length , 1 )
SiteSetting . refresh!
end
after do
SiteSetting . defaults . set_regardless_of_locale ( :min_search_term_length , @old_default )
SiteSetting . refresh!
end
it 'finds posts containing Japanese text if tokenization is forced' do
SiteSetting . search_tokenize_japanese = true
expect ( Search . execute ( '日本' ) . posts . map ( & :id ) ) . to eq ( [ post_2 . id , post . id ] )
expect ( Search . execute ( '日' ) . posts . map ( & :id ) ) . to eq ( [ post_2 . id , post . id ] )
end
it " find posts containing search term when site's locale is set to Japanese " do
SiteSetting . default_locale = 'ja'
expect ( Search . execute ( '日本' ) . posts . map ( & :id ) ) . to eq ( [ post_2 . id , post . id ] )
expect ( Search . execute ( '日' ) . posts . map ( & :id ) ) . to eq ( [ post_2 . id , post . id ] )
end
it 'does not include superfluous spaces in blurbs' do
SiteSetting . default_locale = 'ja'
post . update! ( raw : '場サアマネ織企ういかせ竹域ヱイマ穂基ホ神3予読ずねいぱ松査ス禁多サウ提懸イふ引小43改こょドめ。深とつぐ主思料農ぞかル者杯検める活分えほづぼ白犠' )
results = Search . execute ( 'ういかせ竹域' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
expect ( results . blurb ( results . posts . first ) ) . to include ( 'ういかせ竹域' )
end
end
end
describe 'Chinese search' do
let ( :sentence ) { 'Discourse is a software company 中国的基础设施网络正在组装。' }
let ( :sentence_t ) { 'Discourse is a software company 太平山森林遊樂區。' }
it 'splits English / Chinese and filter out Chinese stop words' do
SiteSetting . default_locale = 'zh_CN'
data = Search . prepare_data ( sentence )
expect ( data ) . to eq ( " Discourse is a software company 中国 基础设施 网络 正在 组装 " )
end
it 'splits for indexing and filter out stop words' do
SiteSetting . default_locale = 'zh_CN'
data = Search . prepare_data ( sentence , :index )
expect ( data ) . to eq ( " Discourse is a software company 中国 基础设施 网络 正在 组装 " )
end
it 'splits English / Traditional Chinese and filter out stop words' do
SiteSetting . default_locale = 'zh_TW'
data = Search . prepare_data ( sentence_t )
expect ( data ) . to eq ( " Discourse is a software company 太平山 森林 遊樂區 " )
end
it 'does not split strings beginning with numeric chars into different segments' do
SiteSetting . default_locale = 'zh_TW'
data = Search . prepare_data ( " #{ sentence } 123abc " )
expect ( data ) . to eq ( " Discourse is a software company 中国 基础设施 网络 正在 组装 123abc " )
end
it 'finds chinese topic based on title' do
SiteSetting . default_locale = 'zh_TW'
SiteSetting . min_search_term_length = 1
topic = Fabricate ( :topic , title : 'My Title Discourse社區指南' )
post = Fabricate ( :post , topic : topic )
expect ( Search . execute ( '社區指南' ) . posts . first . id ) . to eq ( post . id )
expect ( Search . execute ( '指南' ) . posts . first . id ) . to eq ( post . id )
end
it 'finds chinese topic based on title if tokenization is forced' do
begin
SiteSetting . search_tokenize_chinese = true
default_min_search_term_length = SiteSetting . defaults . get ( :min_search_term_length )
SiteSetting . defaults . set_regardless_of_locale ( :min_search_term_length , 1 )
SiteSetting . refresh!
topic = Fabricate ( :topic , title : 'My Title Discourse社區指南' )
post = Fabricate ( :post , topic : topic )
expect ( Search . execute ( '社區指南' ) . posts . first . id ) . to eq ( post . id )
expect ( Search . execute ( '指南' ) . posts . first . id ) . to eq ( post . id )
ensure
if default_min_search_term_length
SiteSetting . defaults . set_regardless_of_locale ( :min_search_term_length , default_min_search_term_length )
SiteSetting . refresh!
end
end
end
end
describe 'Advanced search' do
it 'supports pinned' do
Fabricate ( :post , raw : 'hi this is a test 123 123' , topic : topic )
_post = Fabricate ( :post , raw : 'boom boom shake the room' , topic : topic )
topic . update_pinned ( true )
expect ( Search . execute ( 'boom in:pinned' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'boom IN:PINNED' ) . posts . length ) . to eq ( 1 )
end
it 'supports wiki' do
topic_2 = Fabricate ( :topic )
post = Fabricate ( :post , raw : 'this is a test 248' , wiki : true , topic : topic )
Fabricate ( :post , raw : 'this is a test 248' , wiki : false , topic : topic_2 )
expect ( Search . execute ( 'test 248' ) . posts . length ) . to eq ( 2 )
expect ( Search . execute ( 'test 248 in:wiki' ) . posts . first ) . to eq ( post )
expect ( Search . execute ( 'test 248 IN:WIKI' ) . posts . first ) . to eq ( post )
end
it 'supports searching for posts that the user has seen/unseen' do
topic_2 = Fabricate ( :topic )
post = Fabricate ( :post , raw : 'logan is longan' , topic : topic )
post_2 = Fabricate ( :post , raw : 'longan is logan' , topic : topic_2 )
[ post . user , topic . user ] . each do | user |
PostTiming . create! (
post_number : post . post_number ,
topic : topic ,
user : user ,
msecs : 1
)
end
expect ( post . seen? ( post . user ) ) . to eq ( true )
expect ( Search . execute ( 'longan' ) . posts . sort ) . to eq ( [ post , post_2 ] )
expect ( Search . execute ( 'longan in:seen' , guardian : Guardian . new ( post . user ) ) . posts )
. to eq ( [ post ] )
expect ( Search . execute ( 'longan IN:SEEN' , guardian : Guardian . new ( post . user ) ) . posts )
. to eq ( [ post ] )
expect ( Search . execute ( 'longan in:seen' ) . posts . sort ) . to eq ( [ post , post_2 ] )
expect ( Search . execute ( 'longan in:seen' , guardian : Guardian . new ( post_2 . user ) ) . posts )
. to eq ( [ ] )
expect ( Search . execute ( 'longan' , guardian : Guardian . new ( post_2 . user ) ) . posts . sort )
. to eq ( [ post , post_2 ] )
expect ( Search . execute ( 'longan in:unseen' , guardian : Guardian . new ( post_2 . user ) ) . posts . sort )
. to eq ( [ post , post_2 ] )
expect ( Search . execute ( 'longan in:unseen' , guardian : Guardian . new ( post . user ) ) . posts )
. to eq ( [ post_2 ] )
expect ( Search . execute ( 'longan IN:UNSEEN' , guardian : Guardian . new ( post . user ) ) . posts )
. to eq ( [ post_2 ] )
end
it 'supports before and after filters' do
time = Time . zone . parse ( '2001-05-20 2:55' )
freeze_time ( time )
post_1 = Fabricate ( :post , raw : 'hi this is a test 123 123' , created_at : time . months_ago ( 2 ) )
post_2 = Fabricate ( :post , raw : 'boom boom shake the room test' )
expect ( Search . execute ( 'test before:1' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'test before:2001-04-20' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'test before:2001' ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( 'test after:2001' ) . posts ) . to contain_exactly ( post_1 , post_2 )
expect ( Search . execute ( 'test before:monday' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'test after:jan' ) . posts ) . to contain_exactly ( post_1 , post_2 )
end
it 'supports in:first, user:, @username' do
post_1 = Fabricate ( :post , raw : 'hi this is a test 123 123' , topic : topic )
post_2 = Fabricate ( :post , raw : 'boom boom shake the room test' , topic : topic )
expect ( Search . execute ( 'test in:first' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'test IN:FIRST' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'boom' ) . posts ) . to contain_exactly ( post_2 )
expect ( Search . execute ( 'boom in:first' ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( 'boom f' ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( '123 in:first' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( '123 f' ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( 'user:nobody' ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( " user: #{ post_1 . user . username } " ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( " user: #{ post_1 . user_id } " ) . posts ) . to contain_exactly ( post_1 )
expect ( Search . execute ( " @ #{ post_1 . user . username } " ) . posts ) . to contain_exactly ( post_1 )
end
context " searching for posts made by users of a group " do
fab! ( :topic ) { Fabricate ( :topic , created_at : 3 . months . ago ) }
fab! ( :user ) { Fabricate ( :user ) }
fab! ( :user_2 ) { Fabricate ( :user ) }
fab! ( :user_3 ) { Fabricate ( :user ) }
fab! ( :group ) { Fabricate ( :group , name : " Like_a_Boss " ) . tap { | g | g . add ( user ) } }
fab! ( :group_2 ) { Fabricate ( :group ) . tap { | g | g . add ( user_2 ) } }
let! ( :post ) { Fabricate ( :post , raw : 'hi this is a test 123 123' , topic : topic , user : user ) }
let! ( :post_2 ) { Fabricate ( :post , user : user_2 ) }
it 'should not return any posts if group does not exist' do
group . update! (
visibility_level : Group . visibility_levels [ :public ] ,
members_visibility_level : Group . visibility_levels [ :public ]
)
expect ( Search . execute ( 'group:99999' ) . posts ) . to eq ( [ ] )
end
it 'should return the right posts for a public group' do
group . update! (
visibility_level : Group . visibility_levels [ :public ] ,
members_visibility_level : Group . visibility_levels [ :public ]
)
expect ( Search . execute ( 'group:like_a_boss' ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( " group: #{ group . id } " ) . posts ) . to contain_exactly ( post )
end
it " should return the right posts for a public group with members' visibility restricted to logged on users " do
group . update! (
visibility_level : Group . visibility_levels [ :public ] ,
members_visibility_level : Group . visibility_levels [ :logged_on_users ]
)
expect ( Search . execute ( " group: #{ group . id } " ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( " group: #{ group . id } " , guardian : Guardian . new ( user_3 ) ) . posts ) . to contain_exactly ( post )
end
it " should return the right posts for a group with visibility restricted to logged on users with members' visibility restricted to members " do
group . update! (
visibility_level : Group . visibility_levels [ :logged_on_users ] ,
members_visibility_level : Group . visibility_levels [ :members ]
)
expect ( Search . execute ( " group: #{ group . id } " ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( " group: #{ group . id } " , guardian : Guardian . new ( user_3 ) ) . posts ) . to eq ( [ ] )
expect ( Search . execute ( " group: #{ group . id } " , guardian : Guardian . new ( user ) ) . posts ) . to contain_exactly ( post )
end
end
it 'supports badge' do
topic = Fabricate ( :topic , created_at : 3 . months . ago )
post = Fabricate ( :post , raw : 'hi this is a test 123 123' , topic : topic )
badge = Badge . create! ( name : " Like a Boss " , badge_type_id : 1 )
UserBadge . create! ( user_id : post . user_id , badge_id : badge . id , granted_at : 1 . minute . ago , granted_by_id : - 1 )
expect ( Search . execute ( 'badge:"like a boss"' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'BADGE:"LIKE A BOSS"' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'badge:"test"' ) . posts . length ) . to eq ( 0 )
end
it 'can match exact phrases' do
post = Fabricate ( :post , raw : %{ this is a test post with 'a URL https://some.site.com/search?q=test.test.test some random text I have to add } )
post2 = Fabricate ( :post , raw : 'test URL post with' )
expect ( Search . execute ( " test post with 'a URL).posts " ) . posts ) . to eq ( [ post2 , post ] )
expect ( Search . execute ( %{ "test post with 'a URL" } ) . posts ) . to eq ( [ post ] )
expect ( Search . execute ( %{ "https://some.site.com/search?q=test.test.test" } ) . posts ) . to eq ( [ post ] )
expect ( Search . execute ( %{ " with 'a URL https://some.site.com/search?q=test.test.test" } ) . posts ) . to eq ( [ post ] )
end
it 'can search numbers correctly, and match exact phrases' do
post = Fabricate ( :post , raw : '3.0 eta is in 2 days horrah' )
post2 = Fabricate ( :post , raw : '3.0 is eta in 2 days horrah' )
expect ( Search . execute ( '3.0 eta' ) . posts ) . to eq ( [ post , post2 ] )
expect ( Search . execute ( " '3.0 eta' " ) . posts ) . to eq ( [ post , post2 ] )
expect ( Search . execute ( " \" 3.0 eta \" " ) . posts ) . to contain_exactly ( post )
expect ( Search . execute ( '"3.0, eta is"' ) . posts ) . to eq ( [ ] )
end
it 'can find by status' do
public_category = Fabricate ( :category , read_restricted : false )
post = Fabricate ( :post , raw : 'hi this is a test 123 123' )
topic = post . topic
topic . update ( category : public_category )
private_category = Fabricate ( :category , read_restricted : true )
post2 = Fabricate ( :post , raw : 'hi this is another test 123 123' )
second_topic = post2 . topic
second_topic . update ( category : private_category )
_post3 = Fabricate ( :post , raw : " another test! " , user : topic . user , topic : second_topic )
expect ( Search . execute ( 'test status:public' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test status:closed' ) . posts . length ) . to eq ( 0 )
expect ( Search . execute ( 'test status:open' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test STATUS:OPEN' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test posts_count:1' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test min_post_count:1' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test min_posts:1' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test max_posts:2' ) . posts . length ) . to eq ( 1 )
topic . update ( closed : true )
second_topic . update ( category : public_category )
expect ( Search . execute ( 'test status:public' ) . posts . length ) . to eq ( 2 )
expect ( Search . execute ( 'test status:closed' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'status:closed' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test status:open' ) . posts . length ) . to eq ( 1 )
topic . update ( archived : true , closed : false )
second_topic . update ( closed : true )
expect ( Search . execute ( 'test status:archived' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test status:open' ) . posts . length ) . to eq ( 0 )
expect ( Search . execute ( 'test status:noreplies' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'test in:likes' , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 0 )
expect ( Search . execute ( 'test in:posted' , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 2 )
expect ( Search . execute ( 'test In:PoStEd' , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 2 )
in_created = Search . execute ( 'test in:created' , guardian : Guardian . new ( topic . user ) ) . posts
created_by_user = Search . execute ( " test created:@ #{ topic . user . username } " , guardian : Guardian . new ( topic . user ) ) . posts
expect ( in_created . length ) . to eq ( 1 )
expect ( created_by_user . length ) . to eq ( 1 )
expect ( in_created ) . to eq ( created_by_user )
expect ( Search . execute ( " test created:@ #{ second_topic . user . username } " , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 1 )
new_user = Fabricate ( :user )
expect ( Search . execute ( " test created:@ #{ new_user . username } " , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 0 )
TopicUser . change ( topic . user . id , topic . id , notification_level : TopicUser . notification_levels [ :tracking ] )
expect ( Search . execute ( 'test in:watching' , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 0 )
expect ( Search . execute ( 'test in:tracking' , guardian : Guardian . new ( topic . user ) ) . posts . length ) . to eq ( 1 )
end
it 'can find posts with images' do
post_uploaded = Fabricate ( :post_with_uploaded_image )
Fabricate ( :post )
CookedPostProcessor . new ( post_uploaded ) . update_post_image
expect ( Search . execute ( 'with:images' ) . posts . map ( & :id ) ) . to contain_exactly ( post_uploaded . id )
end
it 'can find by latest' do
topic1 = Fabricate ( :topic , title : 'I do not like that Sam I am' )
post1 = Fabricate ( :post , topic : topic1 , created_at : 10 . minutes . ago )
post2 = Fabricate ( :post , raw : 'that Sam I am, that Sam I am' , created_at : 5 . minutes . ago )
expect ( Search . execute ( 'sam' ) . posts . map ( & :id ) ) . to eq ( [ post1 . id , post2 . id ] )
expect ( Search . execute ( 'sam ORDER:LATEST' ) . posts . map ( & :id ) ) . to eq ( [ post2 . id , post1 . id ] )
expect ( Search . execute ( 'sam l' ) . posts . map ( & :id ) ) . to eq ( [ post2 . id , post1 . id ] )
expect ( Search . execute ( 'l sam' ) . posts . map ( & :id ) ) . to eq ( [ post2 . id , post1 . id ] )
end
it 'can order by topic creation' do
today = Date . today
yesterday = 1 . day . ago
two_days_ago = 2 . days . ago
category = Fabricate ( :category_with_definition )
old_topic = Fabricate ( :topic ,
title : 'First Topic, testing the created_at sort' ,
created_at : two_days_ago ,
category : category
)
latest_topic = Fabricate ( :topic ,
title : 'Second Topic, testing the created_at sort' ,
created_at : yesterday ,
category : category
)
old_relevant_topic_post = Fabricate ( :post ,
topic : old_topic ,
created_at : yesterday ,
raw : 'Relevant Relevant Topic'
)
latest_irrelevant_topic_post = Fabricate ( :post ,
topic : latest_topic ,
created_at : today ,
raw : 'Not Relevant'
)
# Expecting the default results
expect ( Search . execute ( 'Topic' ) . posts . map ( & :id ) ) . to eq ( [
old_relevant_topic_post . id ,
latest_irrelevant_topic_post . id ,
category . topic . first_post . id
] )
# Expecting the ordered by topic creation results
expect ( Search . execute ( 'Topic order:latest_topic' ) . posts . map ( & :id ) ) . to eq ( [
category . topic . first_post . id ,
latest_irrelevant_topic_post . id ,
old_relevant_topic_post . id
] )
end
it 'can order by topic views' do
topic = Fabricate ( :topic , views : 1 )
topic2 = Fabricate ( :topic , views : 2 )
post = Fabricate ( :post , raw : 'Topic' , topic : topic )
post2 = Fabricate ( :post , raw : 'Topic' , topic : topic2 )
expect ( Search . execute ( 'Topic order:views' ) . posts . map ( & :id ) ) . to eq ( [
post2 . id ,
post . id
] )
end
it 'can filter by topic views' do
topic = Fabricate ( :topic , views : 100 )
topic2 = Fabricate ( :topic , views : 200 )
post = Fabricate ( :post , raw : 'Topic' , topic : topic )
post2 = Fabricate ( :post , raw : 'Topic' , topic : topic2 )
expect ( Search . execute ( 'Topic min_views:150' ) . posts . map ( & :id ) ) . to eq ( [ post2 . id ] )
expect ( Search . execute ( 'Topic max_views:150' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'can search for terms with dots' do
post = Fabricate ( :post , raw : 'Will.2000 Will.Bob.Bill...' )
expect ( Search . execute ( 'bill' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'bob' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( '2000' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'can search URLS correctly' do
post = Fabricate ( :post , raw : 'i like http://wb.camra.org.uk/latest#test so yay' )
expect ( Search . execute ( 'http://wb.camra.org.uk/latest#test' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'camra' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'http://wb' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'wb.camra' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'wb.camra.org' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'org.uk' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'camra.org.uk' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'wb.camra.org.uk' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'wb.camra.org.uk/latest' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( '/latest#test' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'supports category slug and tags' do
# main category
category = Fabricate ( :category_with_definition , name : 'category 24' , slug : 'cateGory-24' )
topic = Fabricate ( :topic , created_at : 3 . months . ago , category : category )
post = Fabricate ( :post , raw : 'Sams first post' , topic : topic )
expect ( Search . execute ( 'sams post #categoRy-24' ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( " sams post category: #{ category . id } " ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( 'sams post #categoRy-25' ) . posts . length ) . to eq ( 0 )
sub_category = Fabricate ( :category_with_definition , name : 'sub category' , slug : 'sub-category' , parent_category_id : category . id )
second_topic = Fabricate ( :topic , created_at : 3 . months . ago , category : sub_category )
Fabricate ( :post , raw : 'sams second post' , topic : second_topic )
expect ( Search . execute ( " sams post category:categoRY-24 " ) . posts . length ) . to eq ( 2 )
expect ( Search . execute ( " sams post category:=cAtegory-24 " ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( " sams post # category-24 " ) . posts . length ) . to eq ( 2 )
expect ( Search . execute ( " sams post # =category-24 " ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( " sams post # sub-category " ) . posts . length ) . to eq ( 1 )
expect ( Search . execute ( " sams post # categoRY-24:SUB-category " ) . posts . length )
. to eq ( 1 )
# tags
topic . tags = [ Fabricate ( :tag , name : 'alpha' ) , Fabricate ( :tag , name : 'привет' ) , Fabricate ( :tag , name : 'HeLlO' ) ]
expect ( Search . execute ( 'this is a test #alpha' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'this is a test #привет' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'this is a test #hElLo' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
expect ( Search . execute ( 'this is a test #beta' ) . posts . size ) . to eq ( 0 )
end
it 'supports sub-sub category slugs' do
SiteSetting . max_category_nesting = 3
category = Fabricate ( :category , name : 'top' , slug : 'top' )
sub = Fabricate ( :category , name : 'middle' , slug : 'middle' , parent_category_id : category . id )
leaf = Fabricate ( :category , name : 'leaf' , slug : 'leaf' , parent_category_id : sub . id )
topic = Fabricate ( :topic , created_at : 3 . months . ago , category : leaf )
_post = Fabricate ( :post , raw : 'Sams first post' , topic : topic )
expect ( Search . execute ( '#Middle:leaf first post' ) . posts . size ) . to eq ( 1 )
end
it 'correctly handles #symbol when no tag or category match' do
Fabricate ( :post , raw : 'testing #1 #9998' )
results = Search . new ( 'testing #1' ) . execute
expect ( results . posts . length ) . to eq ( 1 )
results = Search . new ( '#9998' ) . execute
expect ( results . posts . length ) . to eq ( 1 )
results = Search . new ( '#777' ) . execute
expect ( results . posts . length ) . to eq ( 0 )
results = Search . new ( 'xxx #:' ) . execute
expect ( results . posts . length ) . to eq ( 0 )
end
context 'tags' do
fab! ( :tag1 ) { Fabricate ( :tag , name : 'lunch' ) }
fab! ( :tag2 ) { Fabricate ( :tag , name : 'eggs' ) }
fab! ( :tag3 ) { Fabricate ( :tag , name : 'sandwiches' ) }
fab! ( :tag_group ) do
group = TagGroup . create! ( name : 'mid day' )
TagGroupMembership . create! ( tag_id : tag1 . id , tag_group_id : group . id )
TagGroupMembership . create! ( tag_id : tag3 . id , tag_group_id : group . id )
group
end
fab! ( :topic1 ) { Fabricate ( :topic , tags : [ tag2 , Fabricate ( :tag ) ] ) }
fab! ( :topic2 ) { Fabricate ( :topic , tags : [ tag2 ] ) }
fab! ( :topic3 ) { Fabricate ( :topic , tags : [ tag1 , tag2 ] ) }
fab! ( :topic4 ) { Fabricate ( :topic , tags : [ tag1 , tag2 , tag3 ] ) }
fab! ( :topic5 ) { Fabricate ( :topic , tags : [ tag2 , tag3 ] ) }
def indexed_post ( * args )
SearchIndexer . enable
Fabricate ( :post , * args )
end
fab! ( :post1 ) { indexed_post ( topic : topic1 ) }
fab! ( :post2 ) { indexed_post ( topic : topic2 ) }
fab! ( :post3 ) { indexed_post ( topic : topic3 ) }
fab! ( :post4 ) { indexed_post ( topic : topic4 ) }
fab! ( :post5 ) { indexed_post ( topic : topic5 ) }
it 'can find posts by tag group' do
expect ( Search . execute ( '#mid-day' ) . posts . map ( & :id ) ) . to eq ( [
post5 , post4 , post3
] . map ( & :id ) )
end
it 'can find posts with tag' do
post4 = Fabricate ( :post , topic : topic3 , raw : " It probably doesn't help that they're green... " )
expect ( Search . execute ( 'green tags:eggs' ) . posts . map ( & :id ) ) . to eq ( [ post4 . id ] )
expect ( Search . execute ( 'tags:plants' ) . posts . size ) . to eq ( 0 )
end
it 'can find posts with non-latin tag' do
topic . tags = [ Fabricate ( :tag , name : 'さようなら' ) ]
post = Fabricate ( :post , raw : 'Testing post' , topic : topic )
expect ( Search . execute ( 'tags:さようなら' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'can find posts with thai tag' do
topic . tags = [ Fabricate ( :tag , name : 'เรซิ่น' ) ]
post = Fabricate ( :post , raw : 'Testing post' , topic : topic )
expect ( Search . execute ( 'tags:เรซิ่น' ) . posts . map ( & :id ) ) . to eq ( [ post . id ] )
end
it 'can find posts with any tag from multiple tags' do
expect ( Search . execute ( 'tags:eggs,lunch' ) . posts . map ( & :id ) . sort ) . to eq ( [ post1 . id , post2 . id , post3 . id , post4 . id , post5 . id ] . sort )
end
it 'can find posts which contains all provided tags' do
expect ( Search . execute ( 'tags:lunch+eggs+sandwiches' ) . posts . map ( & :id ) ) . to eq ( [ post4 . id ] . sort )
expect ( Search . execute ( 'tags:eggs+lunch+sandwiches' ) . posts . map ( & :id ) ) . to eq ( [ post4 . id ] . sort )
end
it 'can find posts which contains provided tags and does not contain selected ones' do
expect ( Search . execute ( 'tags:eggs -tags:lunch' ) . posts . map ( & :id ) )
. to eq ( [ post5 , post2 , post1 ] . map ( & :id ) )
expect ( Search . execute ( 'tags:eggs -tags:lunch+sandwiches' ) . posts . map ( & :id ) )
. to eq ( [ post5 , post3 , post2 , post1 ] . map ( & :id ) )
expect ( Search . execute ( 'tags:eggs -tags:lunch,sandwiches' ) . posts . map ( & :id ) )
. to eq ( [ post2 , post1 ] . map ( & :id ) )
end
it 'orders posts correctly when combining tags with categories or terms' do
cat1 = Fabricate ( :category_with_definition , name : 'food' )
topic6 = Fabricate ( :topic , tags : [ tag1 , tag2 ] , category : cat1 )
topic7 = Fabricate ( :topic , tags : [ tag1 , tag2 , tag3 ] , category : cat1 )
post7 = Fabricate ( :post , topic : topic6 , raw : " Wakey, wakey, eggs and bakey. " , like_count : 5 , created_at : 2 . minutes . ago )
post8 = Fabricate ( :post , topic : topic7 , raw : " Bakey, bakey, eggs to makey. " , like_count : 2 , created_at : 1 . minute . ago )
expect ( Search . execute ( 'bakey tags:lunch order:latest' ) . posts . map ( & :id ) )
. to eq ( [ post8 . id , post7 . id ] )
expect ( Search . execute ( '#food tags:lunch order:latest' ) . posts . map ( & :id ) )
. to eq ( [ post8 . id , post7 . id ] )
expect ( Search . execute ( '#food tags:lunch order:likes' ) . posts . map ( & :id ) )
. to eq ( [ post7 . id , post8 . id ] )
end
end
it " can find posts which contains filetypes " do
post1 = Fabricate ( :post , raw : " http://example.com/image.png " )
post2 = Fabricate ( :post ,
raw : " Discourse logo \n " \
" http://example.com/logo.png \n " \
" http://example.com/vector_image.svg "
)
post_with_upload = Fabricate ( :post , uploads : [ Fabricate ( :upload ) ] )
Fabricate ( :post )
TopicLink . extract_from ( post1 )
TopicLink . extract_from ( post2 )
expect ( Search . execute ( 'filetype:svg' ) . posts ) . to eq ( [ post2 ] )
expect ( Search . execute ( 'filetype:png' ) . posts . map ( & :id ) ) . to eq ( [
post_with_upload , post2 , post1
] . map ( & :id ) )
expect ( Search . execute ( 'logo filetype:png' ) . posts ) . to eq ( [ post2 ] )
end
end
context '#ts_query' do
it 'can parse complex strings using ts_query helper' do
str = + " grigio:babel deprecated? "
str << " page page on Atmosphere](https://atmospherejs.com/grigio/babel)xxx: aaa.js:222 aaa' \" bbb "
ts_query = Search . ts_query ( term : str , ts_config : " simple " )
expect { DB . exec ( + " SELECT to_tsvector('bbb') @@ " << ts_query ) } . to_not raise_error
ts_query = Search . ts_query ( term : " foo.bar/'&baz " , ts_config : " simple " )
expect { DB . exec ( + " SELECT to_tsvector('bbb') @@ " << ts_query ) } . to_not raise_error
expect ( ts_query ) . to include ( " baz " )
end
it 'escapes the term correctly' do
expect ( Search . ts_query ( term : 'Title with trailing backslash\\' ) )
. to eq ( " TO_TSQUERY('english', '''Title with trailing backslash \\ \\ \\ \\ '':*') " )
expect ( Search . ts_query ( term : " Title with trailing quote' " ) )
. to eq ( " TO_TSQUERY('english', '''Title with trailing quote'''''':*') " )
end
end
context '#word_to_date' do
it 'parses relative dates correctly' do
time = Time . zone . parse ( '2001-02-20 2:55' )
freeze_time ( time )
expect ( Search . word_to_date ( 'yesterday' ) ) . to eq ( time . beginning_of_day . yesterday )
expect ( Search . word_to_date ( 'suNday' ) ) . to eq ( Time . zone . parse ( '2001-02-18' ) )
expect ( Search . word_to_date ( 'thursday' ) ) . to eq ( Time . zone . parse ( '2001-02-15' ) )
expect ( Search . word_to_date ( 'deCember' ) ) . to eq ( Time . zone . parse ( '2000-12-01' ) )
expect ( Search . word_to_date ( 'deC' ) ) . to eq ( Time . zone . parse ( '2000-12-01' ) )
expect ( Search . word_to_date ( 'january' ) ) . to eq ( Time . zone . parse ( '2001-01-01' ) )
expect ( Search . word_to_date ( 'jan' ) ) . to eq ( Time . zone . parse ( '2001-01-01' ) )
expect ( Search . word_to_date ( '100' ) ) . to eq ( time . beginning_of_day . days_ago ( 100 ) )
expect ( Search . word_to_date ( 'invalid' ) ) . to eq ( nil )
end
it 'parses absolute dates correctly' do
expect ( Search . word_to_date ( '2001-1-20' ) ) . to eq ( Time . zone . parse ( '2001-01-20' ) )
expect ( Search . word_to_date ( '2030-10-2' ) ) . to eq ( Time . zone . parse ( '2030-10-02' ) )
expect ( Search . word_to_date ( '2030-10' ) ) . to eq ( Time . zone . parse ( '2030-10-01' ) )
expect ( Search . word_to_date ( '2030' ) ) . to eq ( Time . zone . parse ( '2030-01-01' ) )
expect ( Search . word_to_date ( '2030-01-32' ) ) . to eq ( nil )
expect ( Search . word_to_date ( '10000' ) ) . to eq ( nil )
end
end
context " # min_post_id " do
it " returns 0 when prefer_recent_posts is disabled " do
SiteSetting . search_prefer_recent_posts = false
expect ( Search . min_post_id_no_cache ) . to eq ( 0 )
end
it " returns a value when prefer_recent_posts is enabled " do
SiteSetting . search_prefer_recent_posts = true
SiteSetting . search_recent_posts_size = 1
Fabricate ( :post )
p2 = Fabricate ( :post )
expect ( Search . min_post_id_no_cache ) . to eq ( p2 . id )
end
end
context " search_log_id " do
it " returns an id when the search succeeds " do
s = Search . new (
'indiana jones' ,
search_type : :header ,
ip_address : '127.0.0.1'
)
results = s . execute
expect ( results . search_log_id ) . to be_present
end
it " does not log search if search_type is not present " do
s = Search . new ( 'foo bar' , ip_address : '127.0.0.1' )
results = s . execute
expect ( results . search_log_id ) . not_to be_present
end
end
context 'in:title' do
it 'allows for search in title' do
topic = Fabricate ( :topic , title : 'I am testing a title search' )
_post2 = Fabricate ( :post , topic : topic , raw : 'this is the second post' , post_number : 2 )
post = Fabricate ( :post , topic : topic , raw : 'this is the first post' , post_number : 1 )
results = Search . execute ( 'title in:title' )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post . id ] )
results = Search . execute ( 'title iN:tItLe' )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post . id ] )
results = Search . execute ( 'first in:title' )
expect ( results . posts ) . to eq ( [ ] )
end
it 'works irrespective of the order' do
topic = Fabricate ( :topic , title : " A topic about Discourse " )
Fabricate ( :post , topic : topic , raw : " This is another post " )
topic2 = Fabricate ( :topic , title : " This is another topic " )
Fabricate ( :post , topic : topic2 , raw : " Discourse is awesome " )
results = Search . execute ( 'Discourse in:title status:open' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'in:title status:open Discourse' )
expect ( results . posts . length ) . to eq ( 1 )
end
end
context 'ignore_diacritics' do
before { SiteSetting . search_ignore_accents = true }
let! ( :post1 ) { Fabricate ( :post , raw : 'สวัสดี Rágis hello' ) }
it ( 'allows strips correctly' ) do
results = Search . execute ( 'hello' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'ragis' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'Rágis' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
# TODO: this is a test we need to fix!
#expect(results.blurb(results.posts.first)).to include('Rágis')
results = Search . execute ( 'สวัสดี' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
end
end
context 'include_diacritics' do
before { SiteSetting . search_ignore_accents = false }
let! ( :post1 ) { Fabricate ( :post , raw : 'สวัสดี Régis hello' ) }
it ( 'allows strips correctly' ) do
results = Search . execute ( 'hello' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'regis' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 0 )
results = Search . execute ( 'Régis' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
expect ( results . blurb ( results . posts . first ) ) . to include ( 'Régis' )
results = Search . execute ( 'สวัสดี' , type_filter : 'topic' )
expect ( results . posts . length ) . to eq ( 1 )
end
end
context 'pagination' do
let ( :number_of_results ) { 2 }
let! ( :post1 ) { Fabricate ( :post , raw : 'hello hello hello hello hello' ) }
let! ( :post2 ) { Fabricate ( :post , raw : 'hello hello hello hello' ) }
let! ( :post3 ) { Fabricate ( :post , raw : 'hello hello hello' ) }
let! ( :post4 ) { Fabricate ( :post , raw : 'hello hello' ) }
let! ( :post5 ) { Fabricate ( :post , raw : 'hello' ) }
before do
Search . stubs ( :per_filter ) . returns ( number_of_results )
end
it 'returns more results flag' do
results = Search . execute ( 'hello' , type_filter : 'topic' )
results2 = Search . execute ( 'hello' , type_filter : 'topic' , page : 2 )
expect ( results . posts . length ) . to eq ( number_of_results )
expect ( results . posts . map ( & :id ) ) . to eq ( [ post1 . id , post2 . id ] )
expect ( results . more_full_page_results ) . to eq ( true )
expect ( results2 . posts . length ) . to eq ( number_of_results )
expect ( results2 . posts . map ( & :id ) ) . to eq ( [ post3 . id , post4 . id ] )
expect ( results2 . more_full_page_results ) . to eq ( true )
end
it 'correctly search with page parameter' do
search = Search . new ( 'hello' , type_filter : 'topic' , page : 3 )
results = search . execute
expect ( search . offset ) . to eq ( 2 * number_of_results )
expect ( results . posts . length ) . to eq ( 1 )
expect ( results . posts ) . to eq ( [ post5 ] )
expect ( results . more_full_page_results ) . to eq ( nil )
end
end
context 'in:tagged' do
it 'allows for searching by presence of any tags' do
topic = Fabricate ( :topic , title : 'I am testing a tagged search' )
_post = Fabricate ( :post , topic : topic , raw : 'this is the first post' )
tag = Fabricate ( :tag )
_topic_tag = Fabricate ( :topic_tag , topic : topic , tag : tag )
results = Search . execute ( 'in:untagged' )
expect ( results . posts . length ) . to eq ( 0 )
results = Search . execute ( 'in:tagged' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'In:TaGgEd' )
expect ( results . posts . length ) . to eq ( 1 )
end
end
context 'in:untagged' do
it 'allows for searching by presence of no tags' do
topic = Fabricate ( :topic , title : 'I am testing a untagged search' )
_post = Fabricate ( :post , topic : topic , raw : 'this is the first post' )
results = Search . execute ( 'iN:uNtAgGeD' )
expect ( results . posts . length ) . to eq ( 1 )
results = Search . execute ( 'in:tagged' )
expect ( results . posts . length ) . to eq ( 0 )
end
end
context 'plugin extensions' do
let! ( :post0 ) { Fabricate ( :post , raw : 'this is the first post about advanced filter with length more than 50 chars' ) }
let! ( :post1 ) { Fabricate ( :post , raw : 'this is the second post about advanced filter' ) }
it 'allows to define custom filter' do
expect ( Search . new ( " advanced " ) . execute . posts ) . to eq ( [ post1 , post0 ] )
Search . advanced_filter ( / ^min_chars:( \ d+)$ / ) do | posts , match |
posts . where ( " (SELECT LENGTH(p2.raw) FROM posts p2 WHERE p2.id = posts.id) >= ? " , match . to_i )
end
expect ( Search . new ( " advanced min_chars:50 " ) . execute . posts ) . to eq ( [ post0 ] )
end
it 'allows to define custom order' do
expect ( Search . new ( " advanced " ) . execute . posts ) . to eq ( [ post1 , post0 ] )
Search . advanced_order ( :chars ) do | posts |
posts . reorder ( " MAX(LENGTH(posts.raw)) DESC " )
end
expect ( Search . new ( " advanced order:chars " ) . execute . posts ) . to eq ( [ post0 , post1 ] )
end
end
context 'exclude_topics filter' do
before { SiteSetting . tagging_enabled = true }
let! ( :user ) { Fabricate ( :user ) }
fab! ( :group ) { Fabricate ( :group , name : 'bruce-world-fans' ) }
fab! ( :topic ) { Fabricate ( :topic , title : 'Bruce topic not a result' ) }
it 'works' do
category = Fabricate ( :category_with_definition , name : 'bruceland' , user : user )
tag = Fabricate ( :tag , name : 'brucealicious' )
result = Search . execute ( 'bruce' , type_filter : 'exclude_topics' )
expect ( result . users . map ( & :id ) ) . to contain_exactly ( user . id )
expect ( result . categories . map ( & :id ) ) . to contain_exactly ( category . id )
expect ( result . groups . map ( & :id ) ) . to contain_exactly ( group . id )
expect ( result . tags . map ( & :id ) ) . to contain_exactly ( tag . id )
expect ( result . posts . length ) . to eq ( 0 )
end
it 'does not fail when parsed term is empty' do
result = Search . execute ( '#cat ' , type_filter : 'exclude_topics' )
expect ( result . categories . length ) . to eq ( 0 )
end
end
2019-05-27 09:52:09 -04:00
end