search_index_backend_spec.rb 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe SearchIndexBackend do
  4. before do |example|
  5. next if !example.metadata[:searchindex]
  6. searchindex_model_reload([Ticket, User, Organization])
  7. end
  8. describe '.build_query' do
  9. subject(:query) { described_class.build_query('Ticket', '', query_extension: params) }
  10. let(:params) { { 'bool' => { 'filter' => { 'term' => { 'a' => 'b' } } } } }
  11. it 'coerces :query_extension hash keys to symbols' do
  12. expect(query.dig(:query, :bool, :filter, :term, :a)).to eq('b')
  13. end
  14. end
  15. describe '.search', searchindex: true do
  16. context 'query finds results' do
  17. let(:record_type) { 'Ticket'.freeze }
  18. let(:record) { create(:ticket) }
  19. before do
  20. record.search_index_update_backend
  21. described_class.refresh
  22. end
  23. it 'finds added records' do
  24. result = described_class.search(record.number, record_type, sort_by: ['updated_at'], order_by: ['desc'])
  25. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  26. end
  27. end
  28. context 'when search for user firstname + double lastname' do
  29. let(:record_type) { 'User'.freeze }
  30. let(:record) { create(:user, login: 'a', email: 'a@a.de', firstname: 'AnFirst', lastname: 'ASplit Lastname') }
  31. before do
  32. record.search_index_update_backend
  33. described_class.refresh
  34. end
  35. it 'finds user record' do
  36. result = described_class.search('AnFirst ASplit Lastname', record_type, sort_by: ['updated_at'], order_by: ['desc'])
  37. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  38. end
  39. end
  40. context 'for query with no results' do
  41. subject(:search) { described_class.search(query, index, limit: 3000) }
  42. let(:query) { 'preferences.notification_sound.enabled:*' }
  43. context 'on a single index' do
  44. let(:index) { 'User' }
  45. it { is_expected.to be_an(Array).and be_empty }
  46. end
  47. context 'on multiple indices' do
  48. let(:index) { %w[User Organization] }
  49. it { is_expected.to be_an(Array).and not_include(nil).and be_empty }
  50. end
  51. context 'when user has a signature detection' do
  52. let(:user) { create(:agent, preferences: { signature_detection: 'Hamburg' }) }
  53. let(:record_type) { 'Ticket'.freeze }
  54. let(:record) { create(:ticket, created_by: user) }
  55. before do
  56. record.search_index_update_backend
  57. described_class.refresh
  58. end
  59. it 'does not find the ticket record' do
  60. result = described_class.search('Hamburg', record_type, sort_by: ['updated_at'], order_by: ['desc'])
  61. expect(result).to eq([])
  62. end
  63. end
  64. end
  65. context 'search with date that requires time zone conversion', time_zone: 'Europe/Vilnius' do
  66. let(:record_type) { 'Ticket'.freeze }
  67. let(:record) { create(:ticket) }
  68. before do
  69. travel_to(Time.zone.parse('2019-01-02 00:33'))
  70. described_class.add(record_type, record)
  71. described_class.refresh
  72. end
  73. it 'finds record in a given timezone with a range' do
  74. Setting.set('timezone_default', 'UTC')
  75. result = described_class.search('created_at: [2019-01-01 TO 2019-01-01]', record_type)
  76. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  77. end
  78. it 'finds record in a far away timezone with a date' do
  79. Setting.set('timezone_default', 'Europe/Vilnius')
  80. result = described_class.search('created_at: 2019-01-02', record_type)
  81. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  82. end
  83. it 'finds record in UTC with date' do
  84. Setting.set('timezone_default', 'UTC')
  85. result = described_class.search('created_at: 2019-01-01', record_type)
  86. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  87. end
  88. end
  89. context 'does find integer values for ticket data', db_strategy: :reset do
  90. let(:record_type) { 'Ticket'.freeze }
  91. let(:record) { create(:ticket, inttest: '1021052349') }
  92. before do
  93. create(:object_manager_attribute_integer, name: 'inttest', data_option: {
  94. 'default' => 0,
  95. 'min' => 0,
  96. 'max' => 99_999_999,
  97. })
  98. ObjectManager::Attribute.migration_execute
  99. record.search_index_update_backend
  100. described_class.refresh
  101. end
  102. it 'finds added records by integer part' do
  103. result = described_class.search('102105', record_type, sort_by: ['updated_at'], order_by: ['desc'])
  104. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  105. end
  106. it 'finds added records by integer' do
  107. result = described_class.search('1021052349', record_type, sort_by: ['updated_at'], order_by: ['desc'])
  108. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  109. end
  110. it 'finds added records by quoted integer' do
  111. result = described_class.search('"1021052349"', record_type, sort_by: ['updated_at'], order_by: ['desc'])
  112. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  113. end
  114. end
  115. context 'can sort by datetime fields', db_strategy: :reset do
  116. let(:record_type) { 'Ticket'.freeze }
  117. let(:record) { create(:ticket) }
  118. let(:field_name) { SecureRandom.uuid }
  119. before do
  120. create(:object_manager_attribute_datetime, name: field_name)
  121. ObjectManager::Attribute.migration_execute
  122. record.search_index_update_backend
  123. described_class.refresh
  124. end
  125. it 'finds added records' do
  126. result = described_class.search(record.number, record_type, sort_by: [field_name], order_by: ['desc'])
  127. expect(result).to eq([{ id: record.id.to_s, type: record_type }])
  128. end
  129. end
  130. end
  131. describe '.append_wildcard_to_simple_query' do
  132. context 'with "simple" queries' do
  133. let(:queries) { <<~QUERIES.lines.map { |x| x.split('#')[0] }.map(&:strip) }
  134. M
  135. Max
  136. Max. # dot and underscore are acceptable characters in simple queries
  137. A_
  138. A_B
  139. äöü
  140. 123
  141. *ax # wildcards are allowed in simple queries
  142. Max*
  143. M*x
  144. M?x
  145. test@example.com
  146. test@example.
  147. test@example
  148. test@
  149. QUERIES
  150. it 'appends a * to the original query' do
  151. expect(queries.map { |query| described_class.append_wildcard_to_simple_query(query) })
  152. .to eq(queries.map { |q| "#{q}*" })
  153. end
  154. end
  155. context 'with "complex" queries (using search operators)' do
  156. let(:queries) { <<~QUERIES.lines.map { |x| x.split('#')[0] }.map(&:strip) }
  157. title:"some words with spaces" # exact phrase / without quotation marks " an AND search for the words will be performed (in Zammad 1.5 and lower an OR search will be performed)
  158. title:"some wor*" # exact phrase beginning with "some wor*" will be searched
  159. created_at:[2017-01-01 TO 2017-12-31] # a time range
  160. created_at:>now-1h # created within last hour
  161. state:new OR state:open
  162. (state:new OR state:open) OR priority:"3 normal"
  163. (state:new OR state:open) AND customer.lastname:smith
  164. state:(new OR open) AND title:(full text search) # state: new OR open & title: full OR text OR search
  165. tags: "some tag"
  166. owner.email: "bod@example.com" AND state: (new OR open OR pending*) # show all open tickets of a certain agent
  167. state:closed AND _missing_:tag # all closed objects without tags
  168. article_count: [1 TO 5] # tickets with 1 to 5 articles
  169. article_count: [10 TO *] # tickets with 10 or more articles
  170. article.from: bob # also article.from can be used
  171. article.body: heat~ # using the fuzzy operator will also find terms that are similar, in this case also "head"
  172. article.body: /joh?n(ath[oa]n)/ # using regular expressions
  173. user:M
  174. user:Max
  175. user:Max.
  176. user:Max*
  177. organization:A_B
  178. organization:A_B*
  179. user: M
  180. user: Max
  181. user: Max.
  182. user: Max*
  183. organization: A_B
  184. organization: A_B*
  185. id:123
  186. number:123
  187. id:"123"
  188. number:"123"
  189. QUERIES
  190. it 'returns the original query verbatim' do
  191. expect(queries.map { |query| described_class.append_wildcard_to_simple_query(query) })
  192. .to eq(queries)
  193. end
  194. end
  195. end
  196. describe '.remove', searchindex: true do
  197. context 'record gets deleted' do
  198. let(:record_type) { 'Ticket'.freeze }
  199. let(:deleted_record) { create(:ticket) }
  200. before do
  201. described_class.add(record_type, deleted_record)
  202. described_class.refresh
  203. end
  204. it 'removes record from search index' do
  205. described_class.remove(record_type, deleted_record.id)
  206. described_class.refresh
  207. result = described_class.search(deleted_record.number, record_type, sort_by: ['updated_at'], order_by: ['desc'])
  208. expect(result).to eq([])
  209. end
  210. context 'other records present' do
  211. let(:other_record) { create(:ticket) }
  212. before do
  213. described_class.add(record_type, other_record)
  214. described_class.refresh
  215. end
  216. it "doesn't remove other records" do
  217. described_class.remove(record_type, deleted_record.id)
  218. described_class.refresh
  219. result = described_class.search(other_record.number, record_type, sort_by: ['updated_at'], order_by: ['desc'])
  220. expect(result).to eq([{ id: other_record.id.to_s, type: record_type }])
  221. end
  222. end
  223. end
  224. end
  225. describe '.selectors', searchindex: true do
  226. let(:group1) { create(:group) }
  227. let(:organization1) { create(:organization, note: 'hihi') }
  228. let(:agent1) { create(:agent, organization: organization1, groups: [group1]) }
  229. let(:customer1) { create(:customer, organization: organization1, firstname: 'special-first-name') }
  230. let(:ticket1) do
  231. ticket = create(:ticket, title: 'some-title1', state_id: 1, created_by: agent1, group: group1)
  232. ticket.tag_add('t1', 1)
  233. ticket
  234. end
  235. let(:ticket2) do
  236. ticket = create(:ticket, title: 'some_title2', state_id: 4)
  237. ticket.tag_add('t2', 1)
  238. ticket
  239. end
  240. let(:ticket3) do
  241. ticket = create(:ticket, title: 'some::title3', state_id: 1)
  242. ticket.tag_add('t1', 1)
  243. ticket.tag_add('t2', 1)
  244. ticket
  245. end
  246. let(:ticket4) { create(:ticket, title: 'phrase some-title4', state_id: 1) }
  247. let(:ticket5) { create(:ticket, title: 'phrase some_title5', state_id: 1) }
  248. let(:ticket6) { create(:ticket, title: 'phrase some::title6', state_id: 1) }
  249. let(:ticket7) { create(:ticket, title: 'some title7', state_id: 1) }
  250. let(:ticket8) { create(:ticket, title: 'sometitle', group: group1, state_id: 1, owner: agent1, customer: customer1, organization: organization1) }
  251. let(:article8) { create(:ticket_article, ticket: ticket8, subject: 'lorem ipsum') }
  252. before do
  253. Ticket.destroy_all # needed to remove not created tickets
  254. travel(-1.hour)
  255. create(:mention, mentionable: ticket1, user: agent1)
  256. ticket1.search_index_update_backend
  257. travel 1.hour
  258. ticket2.search_index_update_backend
  259. travel 1.second
  260. ticket3.search_index_update_backend
  261. travel 1.second
  262. ticket4.search_index_update_backend
  263. travel 1.second
  264. ticket5.search_index_update_backend
  265. travel 1.second
  266. ticket6.search_index_update_backend
  267. travel 1.second
  268. ticket7.search_index_update_backend
  269. travel 1.hour
  270. article8.ticket.search_index_update_backend
  271. described_class.refresh
  272. end
  273. context 'when limit is used' do
  274. it 'finds 1 record' do
  275. result = described_class.selectors('Ticket', { 'ticket.created_at'=>{ 'operator' => 'till (relative)', 'value' => '30', 'range' => 'minute' } }, { limit: 1 }, { field: 'created_at' })
  276. expect(result[:object_ids].count).to eq(1)
  277. end
  278. it 'finds 3 records' do
  279. result = described_class.selectors('Ticket', { 'ticket.created_at'=>{ 'operator' => 'till (relative)', 'value' => '30', 'range' => 'minute' } }, { limit: 3 }, { field: 'created_at' })
  280. expect(result[:object_ids].count).to eq(3)
  281. end
  282. end
  283. context 'query with contains' do
  284. it 'finds records with till (relative)' do
  285. result = described_class.selectors('Ticket',
  286. { 'ticket.created_at'=>{ 'operator' => 'till (relative)', 'value' => '30', 'range' => 'minute' } },
  287. {},
  288. {
  289. field: 'created_at', # sort to verify result
  290. })
  291. expect(result).to eq({ count: 7, object_ids: [ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  292. end
  293. # https://github.com/zammad/zammad/issues/5105
  294. context 'with non-overlapping aggs_interval' do
  295. before do
  296. travel_to 18.months.from_now
  297. create(:ticket)
  298. searchindex_model_reload([Ticket])
  299. travel_back
  300. end
  301. it 'finds no records' do
  302. result = described_class.selectors('Ticket',
  303. { 'ticket.created_at'=>{ 'operator' => 'till (relative)', 'value' => '30', 'range' => 'minute' } },
  304. {},
  305. {
  306. from: 1.year.from_now,
  307. to: 2.years.from_now,
  308. interval: 'month', # year, quarter, month, week, day, hour, minute, second
  309. field: 'created_at',
  310. })
  311. expect(result['hits']['total']['value']).to eq(0)
  312. end
  313. end
  314. it 'finds records with from (relative)' do
  315. result = described_class.selectors('Ticket',
  316. { 'ticket.created_at'=>{ 'operator' => 'from (relative)', 'value' => '30', 'range' => 'minute' } },
  317. {},
  318. {
  319. field: 'created_at', # sort to verify result
  320. })
  321. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  322. end
  323. it 'finds records with till (relative) including +1 hour ticket' do
  324. result = described_class.selectors('Ticket',
  325. { 'ticket.created_at'=>{ 'operator' => 'till (relative)', 'value' => '120', 'range' => 'minute' } },
  326. {},
  327. {
  328. field: 'created_at', # sort to verify result
  329. })
  330. expect(result).to eq({ count: 8, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  331. end
  332. it 'finds records with from (relative) including -1 hour ticket' do
  333. result = described_class.selectors('Ticket',
  334. { 'ticket.created_at'=>{ 'operator' => 'from (relative)', 'value' => '120', 'range' => 'minute' } },
  335. {},
  336. {
  337. field: 'created_at', # sort to verify result
  338. })
  339. expect(result).to eq({ count: 8, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  340. end
  341. it 'finds records with tags which contains all' do
  342. result = described_class.selectors('Ticket',
  343. { 'ticket.tags'=>{ 'operator' => 'contains all', 'value' => 't1, t2' } },
  344. {},
  345. {
  346. field: 'created_at', # sort to verify result
  347. })
  348. expect(result).to eq({ count: 1, object_ids: [ticket3.id.to_s] })
  349. end
  350. it 'finds records with tags which contains one' do
  351. result = described_class.selectors('Ticket',
  352. { 'ticket.tags'=>{ 'operator' => 'contains one', 'value' => 't1, t2' } },
  353. {},
  354. {
  355. field: 'created_at', # sort to verify result
  356. })
  357. expect(result).to eq({ count: 3, object_ids: [ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  358. end
  359. it 'finds records with tags which contains all not' do
  360. result = described_class.selectors('Ticket',
  361. { 'ticket.tags'=>{ 'operator' => 'contains all not', 'value' => 't2' } },
  362. {},
  363. {
  364. field: 'created_at', # sort to verify result
  365. })
  366. expect(result).to eq({ count: 6, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket1.id.to_s] })
  367. end
  368. it 'finds records with tags which contains one not' do
  369. result = described_class.selectors('Ticket',
  370. { 'ticket.tags'=>{ 'operator' => 'contains one not', 'value' => 't1' } },
  371. {},
  372. {
  373. field: 'created_at', # sort to verify result
  374. })
  375. expect(result).to eq({ count: 6, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket2.id.to_s] })
  376. end
  377. it 'finds records with organization note' do
  378. result = described_class.selectors('Ticket',
  379. {
  380. 'organization.note' => {
  381. 'operator' => 'contains',
  382. 'value' => 'hihi',
  383. },
  384. },
  385. {},
  386. {
  387. field: 'created_at', # sort to verify result
  388. })
  389. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  390. end
  391. it 'finds records with customer firstname' do
  392. result = described_class.selectors('Ticket',
  393. {
  394. 'customer.firstname' => {
  395. 'operator' => 'contains',
  396. 'value' => 'special',
  397. },
  398. },
  399. {},
  400. {
  401. field: 'created_at', # sort to verify result
  402. })
  403. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  404. end
  405. it 'finds records with article subject' do
  406. result = described_class.selectors('Ticket',
  407. {
  408. 'article.subject' => {
  409. 'operator' => 'contains',
  410. 'value' => 'ipsum',
  411. },
  412. },
  413. {},
  414. {
  415. field: 'created_at', # sort to verify result
  416. })
  417. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  418. end
  419. it 'finds records with pre_condition not_set' do
  420. result = described_class.selectors('Ticket',
  421. {
  422. 'created_by_id' => {
  423. 'pre_condition' => 'not_set',
  424. 'operator' => 'is',
  425. },
  426. },
  427. {},
  428. {
  429. field: 'created_at', # sort to verify result
  430. })
  431. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  432. end
  433. it 'finds records with pre_condition current_user.id' do
  434. result = described_class.selectors('Ticket',
  435. {
  436. 'owner_id' => {
  437. 'pre_condition' => 'current_user.id',
  438. 'operator' => 'is',
  439. },
  440. },
  441. { current_user: agent1 },
  442. {
  443. field: 'created_at', # sort to verify result
  444. })
  445. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  446. end
  447. it 'finds records with pre_condition current_user.organization_id' do
  448. result = described_class.selectors('Ticket',
  449. {
  450. 'organization_id' => {
  451. 'pre_condition' => 'current_user.organization_id',
  452. 'operator' => 'is',
  453. },
  454. },
  455. { current_user: agent1 },
  456. {
  457. field: 'created_at', # sort to verify result
  458. })
  459. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  460. end
  461. it 'finds records with containing phrase' do
  462. result = described_class.selectors('Ticket',
  463. {
  464. 'title' => {
  465. 'operator' => 'contains',
  466. 'value' => 'phrase',
  467. },
  468. },
  469. {},
  470. {
  471. field: 'created_at', # sort to verify result
  472. })
  473. expect(result).to eq({ count: 3, object_ids: [ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s] })
  474. end
  475. it 'finds records with containing some title7' do
  476. result = described_class.selectors('Ticket',
  477. 'title' => {
  478. 'operator' => 'contains',
  479. 'value' => 'some title7',
  480. })
  481. expect(result).to eq({ count: 1, object_ids: [ticket7.id.to_s] })
  482. end
  483. it 'finds records with containing -' do
  484. result = described_class.selectors('Ticket',
  485. 'title' => {
  486. 'operator' => 'contains',
  487. 'value' => 'some-title1',
  488. })
  489. expect(result).to eq({ count: 1, object_ids: [ticket1.id.to_s] })
  490. end
  491. it 'finds records with containing _' do
  492. result = described_class.selectors('Ticket',
  493. 'title' => {
  494. 'operator' => 'contains',
  495. 'value' => 'some_title2',
  496. })
  497. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  498. end
  499. it 'finds records with containing ::' do
  500. result = described_class.selectors('Ticket',
  501. 'title' => {
  502. 'operator' => 'contains',
  503. 'value' => 'some::title3',
  504. })
  505. expect(result).to eq({ count: 1, object_ids: [ticket3.id.to_s] })
  506. end
  507. it 'finds records with containing 4' do
  508. result = described_class.selectors('Ticket',
  509. 'state_id' => {
  510. 'operator' => 'contains',
  511. 'value' => 4,
  512. })
  513. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  514. end
  515. it 'finds records with containing "4"' do
  516. result = described_class.selectors('Ticket',
  517. 'state_id' => {
  518. 'operator' => 'contains',
  519. 'value' => '4',
  520. })
  521. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  522. end
  523. end
  524. context 'query with contains not' do
  525. it 'finds records with containing not phrase' do
  526. result = described_class.selectors('Ticket',
  527. {
  528. 'title' => {
  529. 'operator' => 'contains not',
  530. 'value' => 'phrase',
  531. },
  532. },
  533. {},
  534. {
  535. field: 'created_at', # sort to verify result
  536. })
  537. expect(result).to eq({ count: 5, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  538. end
  539. it 'finds records with containing not some title7' do
  540. result = described_class.selectors('Ticket',
  541. 'title' => {
  542. 'operator' => 'contains not',
  543. 'value' => 'some title7',
  544. })
  545. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  546. end
  547. it 'finds records with containing not -' do
  548. result = described_class.selectors('Ticket',
  549. 'title' => {
  550. 'operator' => 'contains not',
  551. 'value' => 'some-title1',
  552. })
  553. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  554. end
  555. it 'finds records with containing not _' do
  556. result = described_class.selectors('Ticket',
  557. 'title' => {
  558. 'operator' => 'contains not',
  559. 'value' => 'some_title2',
  560. })
  561. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  562. end
  563. it 'finds records with containing not ::' do
  564. result = described_class.selectors('Ticket',
  565. 'title' => {
  566. 'operator' => 'contains not',
  567. 'value' => 'some::title3',
  568. })
  569. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  570. end
  571. it 'finds records with containing not 4' do
  572. result = described_class.selectors('Ticket',
  573. 'state_id' => {
  574. 'operator' => 'contains not',
  575. 'value' => 4,
  576. })
  577. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  578. end
  579. it 'finds records with containing not "4"' do
  580. result = described_class.selectors('Ticket',
  581. 'state_id' => {
  582. 'operator' => 'contains not',
  583. 'value' => '4',
  584. })
  585. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  586. end
  587. end
  588. context 'query with is' do
  589. it 'finds records with is phrase' do
  590. result = described_class.selectors('Ticket',
  591. 'title' => {
  592. 'operator' => 'is',
  593. 'value' => 'phrase',
  594. })
  595. expect(result).to eq({ count: 0, object_ids: [] })
  596. end
  597. it 'finds records with is some title7' do
  598. result = described_class.selectors('Ticket',
  599. 'title' => {
  600. 'operator' => 'is',
  601. 'value' => 'some title7',
  602. })
  603. expect(result).to eq({ count: 1, object_ids: [ticket7.id.to_s] })
  604. end
  605. it 'finds records with is -' do
  606. result = described_class.selectors('Ticket',
  607. 'title' => {
  608. 'operator' => 'is',
  609. 'value' => 'some-title1',
  610. })
  611. expect(result).to eq({ count: 1, object_ids: [ticket1.id.to_s] })
  612. end
  613. it 'finds records with is _' do
  614. result = described_class.selectors('Ticket',
  615. 'title' => {
  616. 'operator' => 'is',
  617. 'value' => 'some_title2',
  618. })
  619. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  620. end
  621. it 'finds records with is ::' do
  622. result = described_class.selectors('Ticket',
  623. 'title' => {
  624. 'operator' => 'is',
  625. 'value' => 'some::title3',
  626. })
  627. expect(result).to eq({ count: 1, object_ids: [ticket3.id.to_s] })
  628. end
  629. it 'finds records with is 4' do
  630. result = described_class.selectors('Ticket',
  631. 'state_id' => {
  632. 'operator' => 'is',
  633. 'value' => 4,
  634. })
  635. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  636. end
  637. it 'finds records with is "4"' do
  638. result = described_class.selectors('Ticket',
  639. 'state_id' => {
  640. 'operator' => 'is',
  641. 'value' => '4',
  642. })
  643. expect(result).to eq({ count: 1, object_ids: [ticket2.id.to_s] })
  644. end
  645. end
  646. context 'query with is not' do
  647. it 'finds records with is not phrase' do
  648. result = described_class.selectors('Ticket',
  649. 'title' => {
  650. 'operator' => 'is not',
  651. 'value' => 'phrase',
  652. })
  653. expect(result).to eq({ count: 8, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  654. end
  655. it 'finds records with is not some title7' do
  656. result = described_class.selectors('Ticket',
  657. 'title' => {
  658. 'operator' => 'is not',
  659. 'value' => 'some title7',
  660. })
  661. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  662. end
  663. it 'finds records with is not -' do
  664. result = described_class.selectors('Ticket',
  665. 'title' => {
  666. 'operator' => 'is not',
  667. 'value' => 'some-title1',
  668. })
  669. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  670. end
  671. it 'finds records with is not _' do
  672. result = described_class.selectors('Ticket',
  673. 'title' => {
  674. 'operator' => 'is not',
  675. 'value' => 'some_title2',
  676. })
  677. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  678. end
  679. it 'finds records with is not ::' do
  680. result = described_class.selectors('Ticket',
  681. 'title' => {
  682. 'operator' => 'is not',
  683. 'value' => 'some::title3',
  684. })
  685. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
  686. end
  687. it 'finds records with is not 4' do
  688. result = described_class.selectors('Ticket',
  689. 'state_id' => {
  690. 'operator' => 'is not',
  691. 'value' => 4,
  692. })
  693. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  694. end
  695. it 'finds records with is not "4"' do
  696. result = described_class.selectors('Ticket',
  697. 'state_id' => {
  698. 'operator' => 'is not',
  699. 'value' => '4',
  700. })
  701. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket1.id.to_s] })
  702. end
  703. it 'finds records with is not state_id ["4"] and title ["sometitle"]' do
  704. result = described_class.selectors('Ticket',
  705. 'state_id' => {
  706. 'operator' => 'is not',
  707. 'value' => ['4'],
  708. },
  709. 'title' => {
  710. 'operator' => 'is',
  711. 'value' => ['sometitle'],
  712. })
  713. expect(result).to eq({ count: 1, object_ids: [ticket8.id.to_s] })
  714. end
  715. end
  716. context 'mentions' do
  717. it 'finds records with pre_condition is not_set' do
  718. result = described_class.selectors('Ticket',
  719. {
  720. 'ticket.mention_user_ids' => {
  721. 'pre_condition' => 'not_set',
  722. 'operator' => 'is',
  723. },
  724. },
  725. { current_user: agent1 },
  726. {
  727. field: 'created_at', # sort to verify result
  728. })
  729. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  730. end
  731. it 'finds records with pre_condition is not not_set' do
  732. result = described_class.selectors('Ticket',
  733. {
  734. 'ticket.mention_user_ids' => {
  735. 'pre_condition' => 'not_set',
  736. 'operator' => 'is not',
  737. },
  738. },
  739. { current_user: agent1 },
  740. {
  741. field: 'created_at', # sort to verify result
  742. })
  743. expect(result).to eq({ count: 1, object_ids: [ticket1.id.to_s] })
  744. end
  745. it 'finds records with pre_condition is current_user.id' do
  746. result = described_class.selectors('Ticket',
  747. {
  748. 'ticket.mention_user_ids' => {
  749. 'pre_condition' => 'current_user.id',
  750. 'operator' => 'is',
  751. },
  752. },
  753. { current_user: agent1 },
  754. {
  755. field: 'created_at', # sort to verify result
  756. })
  757. expect(result).to eq({ count: 1, object_ids: [ticket1.id.to_s] })
  758. end
  759. it 'finds records with pre_condition is not current_user.id' do
  760. result = described_class.selectors('Ticket',
  761. {
  762. 'ticket.mention_user_ids' => {
  763. 'pre_condition' => 'current_user.id',
  764. 'operator' => 'is not',
  765. },
  766. },
  767. { current_user: agent1 },
  768. {
  769. field: 'created_at', # sort to verify result
  770. })
  771. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  772. end
  773. it 'finds records with pre_condition is specific' do
  774. result = described_class.selectors('Ticket',
  775. {
  776. 'ticket.mention_user_ids' => {
  777. 'pre_condition' => 'specific',
  778. 'operator' => 'is',
  779. 'value' => agent1.id,
  780. },
  781. },
  782. {},
  783. {
  784. field: 'created_at', # sort to verify result
  785. })
  786. expect(result).to eq({ count: 1, object_ids: [ticket1.id.to_s] })
  787. end
  788. it 'finds records with pre_condition is not specific' do
  789. result = described_class.selectors('Ticket',
  790. {
  791. 'ticket.mention_user_ids' => {
  792. 'pre_condition' => 'specific',
  793. 'operator' => 'is not',
  794. 'value' => agent1.id,
  795. },
  796. },
  797. {},
  798. {
  799. field: 'created_at', # sort to verify result
  800. })
  801. expect(result).to eq({ count: 7, object_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket3.id.to_s, ticket2.id.to_s] })
  802. end
  803. end
  804. end
  805. describe '.search_by_index_sort' do
  806. it 'does return a sort value' do
  807. expect(described_class.search_by_index_sort(index: 'Ticket', sort_by: ['title'], order_by: 'desc')).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
  808. end
  809. it 'does return a sort value for fulltext searches' do
  810. expect(described_class.search_by_index_sort(index: 'Ticket', sort_by: ['title'], order_by: 'desc', fulltext: true)).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
  811. end
  812. it 'does return a default sort value' do
  813. expect(described_class.search_by_index_sort(index: 'Ticket')).to eq([{ updated_at: { order: 'desc' } }, '_score'])
  814. end
  815. it 'does return a default sort value for fulltext searches' do
  816. expect(described_class.search_by_index_sort(index: 'Ticket', fulltext: true)).to eq(['_score'])
  817. end
  818. end
  819. describe 'SSL verification', searchindex: true do
  820. describe '.make_request' do
  821. def request(verify: false)
  822. Setting.set('es_url', 'https://127.0.0.1:9200')
  823. Setting.set('es_ssl_verify', verify)
  824. SearchIndexBackend.get('Ticket', Ticket.first.id)
  825. end
  826. it 'does verify SSL' do
  827. allow(UserAgent).to receive(:get_http)
  828. request(verify: true)
  829. expect(UserAgent).to have_received(:get_http).with(URI::HTTPS, hash_including(verify_ssl: true)).once
  830. end
  831. it 'does not verify SSL' do
  832. allow(UserAgent).to receive(:get_http)
  833. request
  834. expect(UserAgent).to have_received(:get_http).with(URI::HTTPS, hash_including(verify_ssl: false)).once
  835. end
  836. end
  837. end
  838. end