search_index_backend_spec.rb 47 KB

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