zoom_spec.rb 79 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347
  1. # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
  2. require 'rails_helper'
  3. require 'system/examples/core_workflow_examples'
  4. RSpec.describe 'Ticket zoom', type: :system do
  5. describe 'owner auto-assignment', authenticated_as: :authenticate do
  6. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), state: Ticket::State.find_by(name: 'new')) }
  7. let!(:session_user) { User.find_by(login: 'admin@example.com') }
  8. context 'for agent disabled' do
  9. def authenticate
  10. Setting.set('ticket_auto_assignment', false)
  11. Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } })
  12. Setting.set('ticket_auto_assignment_user_ids_ignore', [])
  13. true
  14. end
  15. it 'do not assign ticket to current session user' do
  16. visit "#ticket/zoom/#{ticket.id}"
  17. within(:active_content) do
  18. expect(page).to have_css('select[name=owner_id]')
  19. expect(page).to have_select('owner_id',
  20. selected: '-',
  21. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  22. end
  23. end
  24. end
  25. context 'for agent enabled' do
  26. def authenticate
  27. Setting.set('ticket_auto_assignment', true)
  28. Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } })
  29. Setting.set('ticket_auto_assignment_user_ids_ignore', setting_user_ids_ignore) if defined?(setting_user_ids_ignore)
  30. true
  31. end
  32. context 'with empty "ticket_auto_assignment_user_ids_ignore"' do
  33. it 'assigns ticket to current session user' do
  34. visit "#ticket/zoom/#{ticket.id}"
  35. within(:active_content) do
  36. expect(page).to have_css('.content.active select[name=owner_id]')
  37. expect(page).to have_select('owner_id',
  38. selected: session_user.fullname,
  39. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  40. end
  41. end
  42. end
  43. context 'with "ticket_auto_assignment_user_ids_ignore" (as integer)' do
  44. let(:setting_user_ids_ignore) { session_user.id }
  45. it 'assigns ticket not to current session user' do
  46. visit "#ticket/zoom/#{ticket.id}"
  47. within(:active_content) do
  48. expect(page).to have_css('select[name=owner_id]')
  49. expect(page).to have_select('owner_id',
  50. selected: '-',
  51. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  52. end
  53. end
  54. end
  55. context 'with "ticket_auto_assignment_user_ids_ignore" (as string)' do
  56. let(:setting_user_ids_ignore) { session_user.id.to_s }
  57. it 'assigns ticket not to current session user' do
  58. visit "#ticket/zoom/#{ticket.id}"
  59. within(:active_content) do
  60. expect(page).to have_css('select[name=owner_id]')
  61. expect(page).to have_select('owner_id',
  62. selected: '-',
  63. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  64. end
  65. end
  66. end
  67. context 'with "ticket_auto_assignment_user_ids_ignore" (as [integer])' do
  68. let(:setting_user_ids_ignore) { [session_user.id] }
  69. it 'assigns ticket not to current session user' do
  70. visit "#ticket/zoom/#{ticket.id}"
  71. within(:active_content) do
  72. expect(page).to have_css('select[name=owner_id]')
  73. expect(page).to have_select('owner_id',
  74. selected: '-',
  75. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  76. end
  77. end
  78. end
  79. context 'with "ticket_auto_assignment_user_ids_ignore" (as [string])' do
  80. let(:setting_user_ids_ignore) { [session_user.id.to_s] }
  81. it 'assigns ticket not to current session user' do
  82. visit "#ticket/zoom/#{ticket.id}"
  83. within(:active_content) do
  84. expect(page).to have_css('select[name=owner_id]')
  85. expect(page).to have_select('owner_id',
  86. selected: '-',
  87. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  88. end
  89. end
  90. end
  91. context 'with "ticket_auto_assignment_user_ids_ignore" and other user ids' do
  92. let(:setting_user_ids_ignore) { [99_999, 999_999] }
  93. it 'assigns ticket to current session user' do
  94. visit "#ticket/zoom/#{ticket.id}"
  95. within(:active_content) do
  96. expect(page).to have_css('select[name=owner_id]')
  97. expect(page).to have_select('owner_id',
  98. selected: session_user.fullname,
  99. options: ['-', 'Agent 1 Test', 'Test Admin Agent'])
  100. end
  101. end
  102. end
  103. end
  104. end
  105. context 'when ticket has an attachment' do
  106. let(:group) { Group.find_by(name: 'Users') }
  107. let(:ticket) { create(:ticket, group: group) }
  108. let(:article) { create(:ticket_article, ticket: ticket) }
  109. let(:attachment_name) { 'some_file.txt' }
  110. before do
  111. Store.add(
  112. object: 'Ticket::Article',
  113. o_id: article.id,
  114. data: 'some content',
  115. filename: attachment_name,
  116. preferences: {
  117. 'Content-Type' => 'text/plain',
  118. },
  119. created_by_id: 1,
  120. )
  121. end
  122. context 'article was already forwarded once' do
  123. before do
  124. visit "#ticket/zoom/#{ticket.id}"
  125. within(:active_content) do
  126. find('a[data-type=emailForward]').click
  127. click('.js-reset')
  128. have_no_css('.js-reset')
  129. end
  130. end
  131. it 'adds attachments when forwarding multiple times' do
  132. within(:active_content) do
  133. find('a[data-type=emailForward]').click
  134. end
  135. within('.js-writeArea') do
  136. expect(page).to have_text attachment_name
  137. end
  138. end
  139. end
  140. end
  141. context 'replying' do
  142. context 'Group without signature' do
  143. let(:ticket) { create(:ticket) }
  144. let(:current_user) { create(:agent, password: 'test', groups: [ticket.group]) }
  145. before do
  146. # initial article to reply to
  147. create(:ticket_article, ticket: ticket)
  148. end
  149. it 'ensures that text input opens on multiple replies', authenticated_as: :current_user do
  150. visit "ticket/zoom/#{ticket.id}"
  151. 2.times do |article_offset|
  152. articles_existing = 1
  153. articles_expected = articles_existing + (article_offset + 1)
  154. all('a[data-type=emailReply]').last.click
  155. # wait till input box expands completely
  156. find('.attachmentPlaceholder-label').in_fixed_position
  157. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  158. find('.articleNewEdit-body').send_keys('Some reply')
  159. click '.js-submit'
  160. expect(page).to have_css('.ticket-article-item', count: articles_expected)
  161. end
  162. end
  163. end
  164. context 'to inbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do
  165. let(:agent) { create(:agent, groups: [Group.first]) }
  166. let(:customer) { create(:customer) }
  167. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  168. let!(:article) { create(:ticket_article, :inbound_phone, ticket: ticket) }
  169. before do
  170. create(:customer, active: false)
  171. end
  172. it 'goes to customer email' do
  173. visit "ticket/zoom/#{ticket.id}"
  174. within :active_ticket_article, article do
  175. click '.js-ArticleAction[data-type=emailReply]'
  176. end
  177. within :active_content do
  178. within '.article-new' do
  179. expect(find('[name=to]', visible: :all).value).to eq customer.email
  180. end
  181. end
  182. end
  183. it 'check active and inactive user in TO-field' do
  184. visit "ticket/zoom/#{ticket.id}"
  185. within :active_ticket_article, article do
  186. click '.js-ArticleAction[data-type=emailReply]'
  187. end
  188. within :active_content do
  189. within '.article-new' do
  190. find('[name=to] ~ .ui-autocomplete-input').fill_in with: '**'
  191. end
  192. end
  193. expect(page).to have_css('ul.ui-autocomplete > li.ui-menu-item', minimum: 2)
  194. expect(page).to have_css('ul.ui-autocomplete > li.ui-menu-item.is-inactive', count: 1)
  195. end
  196. end
  197. context 'to outbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do
  198. let(:agent) { create(:agent, groups: [Group.first]) }
  199. let(:customer) { create(:customer) }
  200. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  201. let!(:article) { create(:ticket_article, :outbound_phone, ticket: ticket) }
  202. it 'goes to customer email' do
  203. visit "ticket/zoom/#{ticket.id}"
  204. within :active_ticket_article, article do
  205. click '.js-ArticleAction[data-type=emailReply]'
  206. end
  207. within :active_content do
  208. within '.article-new' do
  209. expect(find('[name=to]', visible: :all).value).to eq customer.email
  210. end
  211. end
  212. end
  213. end
  214. context 'scrollPageHeader disappears when answering via email #3736' do
  215. let(:ticket) do
  216. ticket = create(:ticket, group: Group.first)
  217. create_list(:ticket_article, 15, ticket: ticket)
  218. ticket
  219. end
  220. before do
  221. visit "ticket/zoom/#{ticket.id}"
  222. end
  223. it 'does reset the scrollPageHeader on rerender of the ticket' do
  224. select User.find_by(email: 'admin@example.com').fullname, from: 'Owner'
  225. find('.js-textarea').send_keys('test 1234')
  226. find('.js-submit').click
  227. expect(page).to have_selector('div.scrollPageHeader .js-ticketTitleContainer')
  228. end
  229. end
  230. end
  231. describe 'delete article', authenticated_as: :authenticate do
  232. let(:group) { Group.first }
  233. let(:admin) { create :admin, groups: [group] }
  234. let(:agent) { create :agent, groups: [group] }
  235. let(:other_agent) { create :agent, groups: [group] }
  236. let(:customer) { create :customer }
  237. let(:article) { send(item) }
  238. def authenticate
  239. Setting.set('ui_ticket_zoom_article_delete_timeframe', setting_delete_timeframe) if defined?(setting_delete_timeframe)
  240. article
  241. user
  242. end
  243. def article_communication
  244. create_ticket_article(sender_name: 'Agent', internal: false, type_name: 'email', updated_by: customer)
  245. end
  246. def article_note_self
  247. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: user)
  248. end
  249. def article_note_other
  250. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: other_agent)
  251. end
  252. def article_note_customer
  253. create_ticket_article(sender_name: 'Customer', internal: false, type_name: 'note', updated_by: customer)
  254. end
  255. def article_note_communication_self
  256. create(:ticket_article_type, name: 'note_communication', communication: true)
  257. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: user)
  258. end
  259. def article_note_communication_other
  260. create(:ticket_article_type, name: 'note_communication', communication: true)
  261. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: other_agent)
  262. end
  263. def create_ticket_article(sender_name:, internal:, type_name:, updated_by:)
  264. UserInfo.current_user_id = updated_by.id
  265. ticket = create :ticket, group: group, customer: customer
  266. create(:ticket_article,
  267. sender_name: sender_name, internal: internal, type_name: type_name, ticket: ticket,
  268. body: "to be deleted #{offset} #{item}",
  269. created_at: offset.ago, updated_at: offset.ago)
  270. end
  271. context 'going through full stack' do
  272. context 'as admin' do
  273. let(:user) { admin }
  274. let(:item) { 'article_note_self' }
  275. let(:offset) { 0.minutes }
  276. it 'succeeds' do
  277. ensure_websocket do
  278. visit "ticket/zoom/#{article.ticket.id}"
  279. end
  280. within :active_ticket_article, article do
  281. click '.js-ArticleAction[data-type=delete]'
  282. end
  283. in_modal do
  284. click '.js-submit'
  285. end
  286. wait.until_disappears { find :active_ticket_article, article, wait: false }
  287. end
  288. end
  289. end
  290. context 'verifying permissions matrix' do
  291. shared_examples 'according to permission matrix' do |item:, expects_visible:, offset:, description:|
  292. context "looking at #{description} #{item}" do
  293. let(:item) { item }
  294. let(:offset) { offset }
  295. let(:matcher) { expects_visible ? :have_css : :have_no_css }
  296. it expects_visible ? 'delete button is visible' : 'delete button is not visible' do
  297. visit "ticket/zoom/#{article.ticket.id}"
  298. within :active_ticket_article, article do
  299. expect(page).to send(matcher, '.js-ArticleAction[data-type=delete]', wait: 0)
  300. end
  301. end
  302. end
  303. end
  304. shared_examples 'deleting ticket article' do |item:, now:, later:, much_later:|
  305. include_examples 'according to permission matrix', item: item, expects_visible: now, offset: 0.minutes, description: 'just created'
  306. include_examples 'according to permission matrix', item: item, expects_visible: later, offset: 6.minutes, description: 'few minutes old'
  307. include_examples 'according to permission matrix', item: item, expects_visible: much_later, offset: 11.minutes, description: 'very old'
  308. end
  309. context 'as admin' do
  310. let(:user) { admin }
  311. include_examples 'deleting ticket article',
  312. item: 'article_communication',
  313. now: false, later: false, much_later: false
  314. include_examples 'deleting ticket article',
  315. item: 'article_note_self',
  316. now: true, later: true, much_later: false
  317. include_examples 'deleting ticket article',
  318. item: 'article_note_other',
  319. now: false, later: false, much_later: false
  320. include_examples 'deleting ticket article',
  321. item: 'article_note_customer',
  322. now: false, later: false, much_later: false
  323. include_examples 'deleting ticket article',
  324. item: 'article_note_communication_self',
  325. now: true, later: true, much_later: false
  326. include_examples 'deleting ticket article',
  327. item: 'article_note_communication_other',
  328. now: false, later: false, much_later: false
  329. end
  330. context 'as agent' do
  331. let(:user) { agent }
  332. include_examples 'deleting ticket article',
  333. item: 'article_communication',
  334. now: false, later: false, much_later: false
  335. include_examples 'deleting ticket article',
  336. item: 'article_note_self',
  337. now: true, later: true, much_later: false
  338. include_examples 'deleting ticket article',
  339. item: 'article_note_other',
  340. now: false, later: false, much_later: false
  341. include_examples 'deleting ticket article',
  342. item: 'article_note_customer',
  343. now: false, later: false, much_later: false
  344. include_examples 'deleting ticket article',
  345. item: 'article_note_communication_self',
  346. now: true, later: true, much_later: false
  347. include_examples 'deleting ticket article',
  348. item: 'article_note_communication_other',
  349. now: false, later: false, much_later: false
  350. end
  351. context 'as customer' do
  352. let(:user) { customer }
  353. include_examples 'deleting ticket article',
  354. item: 'article_communication',
  355. now: false, later: false, much_later: false
  356. include_examples 'deleting ticket article',
  357. item: 'article_note_customer',
  358. now: false, later: false, much_later: false
  359. end
  360. context 'with custom offset' do
  361. let(:setting_delete_timeframe) { 6_000 }
  362. context 'as admin' do
  363. let(:user) { admin }
  364. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  365. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  366. end
  367. context 'as agent' do
  368. let(:user) { agent }
  369. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  370. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  371. end
  372. end
  373. context 'with timeframe as 0' do
  374. let(:setting_delete_timeframe) { 0 }
  375. context 'as agent' do
  376. let(:user) { agent }
  377. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 99.days, description: 'long after'
  378. end
  379. end
  380. end
  381. context 'button is hidden on the go' do
  382. let(:setting_delete_timeframe) { 10 }
  383. let(:user) { agent }
  384. let(:item) { 'article_note_self' }
  385. let!(:article) { send(item) }
  386. let(:offset) { 0.seconds }
  387. it 'successfully' do
  388. visit "ticket/zoom/#{article.ticket.id}"
  389. within :active_ticket_article, article do
  390. find '.js-ArticleAction[data-type=delete]' # make sure delete button did show up
  391. expect(page).to have_no_css('.js-ArticleAction[data-type=delete]')
  392. end
  393. end
  394. end
  395. end
  396. context 'S/MIME active', authenticated_as: :authenticate do
  397. let(:system_email_address) { 'smime1@example.com' }
  398. let(:email_address) { create(:email_address, email: system_email_address) }
  399. let(:group) { create(:group, email_address: email_address) }
  400. let(:agent_groups) { [group] }
  401. let(:agent) { create(:agent, groups: agent_groups) }
  402. let(:sender_email_address) { 'smime2@example.com' }
  403. let(:customer) { create(:customer, email: sender_email_address) }
  404. let!(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }
  405. def authenticate
  406. Setting.set('smime_integration', true)
  407. agent
  408. end
  409. context 'received mail' do
  410. context 'article meta information' do
  411. context 'success' do
  412. it 'shows encryption/sign information' do
  413. create(:ticket_article, preferences: {
  414. security: {
  415. type: 'S/MIME',
  416. encryption: {
  417. success: true,
  418. comment: 'COMMENT_ENCRYPT_SUCCESS',
  419. },
  420. sign: {
  421. success: true,
  422. comment: 'COMMENT_SIGN_SUCCESS',
  423. },
  424. }
  425. }, ticket: ticket)
  426. visit "#ticket/zoom/#{ticket.id}"
  427. expect(page).to have_css('svg.icon-lock')
  428. expect(page).to have_css('svg.icon-signed')
  429. open_article_meta
  430. expect(page).to have_css('span', text: 'Encrypted')
  431. expect(page).to have_css('span', text: 'Signed')
  432. expect(page).to have_css('span[title=COMMENT_ENCRYPT_SUCCESS]')
  433. expect(page).to have_css('span[title=COMMENT_SIGN_SUCCESS]')
  434. end
  435. end
  436. context 'error' do
  437. it 'shows create information about encryption/sign failed' do
  438. create(:ticket_article, preferences: {
  439. security: {
  440. type: 'S/MIME',
  441. encryption: {
  442. success: false,
  443. comment: 'Encryption failed because XXX',
  444. },
  445. sign: {
  446. success: false,
  447. comment: 'Sign failed because XXX',
  448. },
  449. }
  450. }, ticket: ticket)
  451. visit "#ticket/zoom/#{ticket.id}"
  452. expect(page).to have_css('svg.icon-not-signed')
  453. open_article_meta
  454. expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX')
  455. expect(page).to have_css('div.alert.alert--warning', text: 'Sign failed because XXX')
  456. end
  457. end
  458. end
  459. context 'certificate not present at time of arrival' do
  460. it 'retry' do
  461. smime1 = create(:smime_certificate, :with_private, fixture: system_email_address)
  462. smime2 = create(:smime_certificate, :with_private, fixture: sender_email_address)
  463. mail = Channel::EmailBuild.build(
  464. from: sender_email_address,
  465. to: system_email_address,
  466. body: 'somebody with some text',
  467. content_type: 'text/plain',
  468. security: {
  469. type: 'S/MIME',
  470. sign: {
  471. success: true,
  472. },
  473. encryption: {
  474. success: true,
  475. },
  476. },
  477. )
  478. smime1.destroy
  479. smime2.destroy
  480. parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
  481. ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
  482. expect(Ticket::Article.find(article.id).body).to eq('no visible content')
  483. create(:smime_certificate, fixture: sender_email_address)
  484. create(:smime_certificate, :with_private, fixture: system_email_address)
  485. visit "#ticket/zoom/#{ticket.id}"
  486. expect(page).to have_no_css('.article-content', text: 'somebody with some text')
  487. click '.js-securityRetryProcess'
  488. expect(page).to have_css('.article-content', text: 'somebody with some text')
  489. end
  490. end
  491. end
  492. context 'replying', authenticated_as: :setup_and_authenticate do
  493. def setup_and_authenticate
  494. create(:ticket_article, ticket: ticket, from: customer.email)
  495. create(:smime_certificate, :with_private, fixture: system_email_address)
  496. create(:smime_certificate, fixture: sender_email_address)
  497. authenticate
  498. end
  499. it 'plain' do
  500. visit "#ticket/zoom/#{ticket.id}"
  501. all('a[data-type=emailReply]').last.click
  502. find('.articleNewEdit-body').send_keys('Test')
  503. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  504. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  505. click '.js-securityEncrypt'
  506. click '.js-securitySign'
  507. click '.js-submit'
  508. expect(page).to have_css('.ticket-article-item', count: 2)
  509. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil
  510. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil
  511. end
  512. it 'signed' do
  513. visit "#ticket/zoom/#{ticket.id}"
  514. all('a[data-type=emailReply]').last.click
  515. find('.articleNewEdit-body').send_keys('Test')
  516. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  517. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  518. click '.js-securityEncrypt'
  519. click '.js-submit'
  520. expect(page).to have_css('.ticket-article-item', count: 2)
  521. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil
  522. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true
  523. end
  524. it 'encrypted' do
  525. visit "#ticket/zoom/#{ticket.id}"
  526. all('a[data-type=emailReply]').last.click
  527. find('.articleNewEdit-body').send_keys('Test')
  528. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  529. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  530. click '.js-securitySign'
  531. click '.js-submit'
  532. expect(page).to have_css('.ticket-article-item', count: 2)
  533. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true
  534. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil
  535. end
  536. it 'signed and encrypted' do
  537. visit "#ticket/zoom/#{ticket.id}"
  538. all('a[data-type=emailReply]').last.click
  539. find('.articleNewEdit-body').send_keys('Test')
  540. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  541. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  542. click '.js-submit'
  543. expect(page).to have_css('.ticket-article-item', count: 2)
  544. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true
  545. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true
  546. end
  547. end
  548. context 'Group default behavior' do
  549. let(:smime_config) { {} }
  550. def authenticate
  551. Setting.set('smime_integration', true)
  552. Setting.set('smime_config', smime_config)
  553. create(:ticket_article, ticket: ticket, from: customer.email)
  554. create(:smime_certificate, :with_private, fixture: system_email_address)
  555. create(:smime_certificate, fixture: sender_email_address)
  556. agent
  557. end
  558. shared_examples 'security defaults example' do |sign:, encrypt:|
  559. it "security defaults sign: #{sign}, encrypt: #{encrypt}" do
  560. within(:active_content) do
  561. encrypt_button = find('.js-securityEncrypt', wait: 5)
  562. sign_button = find('.js-securitySign', wait: 5)
  563. active_button_class = '.btn--active'
  564. expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt)
  565. expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign)
  566. end
  567. end
  568. end
  569. shared_examples 'security defaults' do |sign:, encrypt:|
  570. before do
  571. visit "#ticket/zoom/#{ticket.id}"
  572. within(:active_content) do
  573. all('a[data-type=emailReply]').last.click
  574. find('.articleNewEdit-body').send_keys('Test')
  575. end
  576. end
  577. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  578. end
  579. shared_examples 'security defaults group change' do |sign:, encrypt:|
  580. before do
  581. visit "#ticket/zoom/#{ticket.id}"
  582. within(:active_content) do
  583. all('a[data-type=emailReply]').last.click
  584. find('.articleNewEdit-body').send_keys('Test')
  585. select new_group.name, from: 'group_id'
  586. end
  587. end
  588. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  589. end
  590. context 'not configured' do
  591. it_behaves_like 'security defaults', sign: true, encrypt: true
  592. end
  593. context 'configuration present' do
  594. let(:smime_config) do
  595. {
  596. 'group_id' => group_defaults
  597. }
  598. end
  599. let(:group_defaults) do
  600. {
  601. 'default_encryption' => {
  602. group.id.to_s => default_encryption,
  603. },
  604. 'default_sign' => {
  605. group.id.to_s => default_sign,
  606. }
  607. }
  608. end
  609. let(:default_sign) { true }
  610. let(:default_encryption) { true }
  611. shared_examples 'sign and encrypt variations' do |check_examples_name|
  612. it_behaves_like check_examples_name, sign: true, encrypt: true
  613. context 'no value' do
  614. let(:group_defaults) { {} }
  615. it_behaves_like check_examples_name, sign: true, encrypt: true
  616. end
  617. context 'signing disabled' do
  618. let(:default_sign) { false }
  619. it_behaves_like check_examples_name, sign: false, encrypt: true
  620. end
  621. context 'encryption disabled' do
  622. let(:default_encryption) { false }
  623. it_behaves_like check_examples_name, sign: true, encrypt: false
  624. end
  625. end
  626. context 'same Group' do
  627. it_behaves_like 'sign and encrypt variations', 'security defaults'
  628. end
  629. context 'Group change' do
  630. let(:new_group) { create(:group, email_address: email_address) }
  631. let(:agent_groups) { [group, new_group] }
  632. let(:group_defaults) do
  633. {
  634. 'default_encryption' => {
  635. new_group.id.to_s => default_encryption,
  636. },
  637. 'default_sign' => {
  638. new_group.id.to_s => default_sign,
  639. }
  640. }
  641. end
  642. it_behaves_like 'sign and encrypt variations', 'security defaults group change'
  643. end
  644. end
  645. end
  646. end
  647. describe 'linking Knowledge Base answer' do
  648. include_context 'basic Knowledge Base'
  649. let(:ticket) { create :ticket, group: Group.find_by(name: 'Users') }
  650. let(:answer) { published_answer }
  651. let(:translation) { answer.translations.first }
  652. shared_examples 'verify linking' do
  653. it 'allows to look up an answer' do
  654. visit "#ticket/zoom/#{ticket.id}"
  655. within :active_content do
  656. within '.link_kb_answers' do
  657. find('.js-add').click
  658. find('.js-input').send_keys translation.title
  659. find(%(li[data-value="#{translation.id}"])).click
  660. expect(find('.link_kb_answers ol')).to have_text translation.title
  661. end
  662. end
  663. end
  664. end
  665. context 'with ES', searchindex: true, authenticated_as: :authenticate do
  666. def authenticate
  667. configure_elasticsearch(required: true, rebuild: true) do
  668. answer
  669. end
  670. true
  671. end
  672. include_examples 'verify linking'
  673. end
  674. context 'without ES', authenticated_as: :authenticate do
  675. def authenticate
  676. answer
  677. true
  678. end
  679. include_examples 'verify linking'
  680. end
  681. end
  682. describe 'forwarding article with an image' do
  683. let(:ticket_article_body) do
  684. filename = 'squares.png'
  685. file = File.binread(Rails.root.join("spec/fixtures/image/#{filename}"))
  686. ext = File.extname(filename)[1...]
  687. base64 = Base64.encode64(file).delete("\n")
  688. "<img style='width: 1004px; max-width: 100%;' src=\\\"data:image/#{ext};base64,#{base64}\\\"><br>"
  689. end
  690. def current_ticket
  691. Ticket.find current_url.split('/').last
  692. end
  693. def create_ticket
  694. visit '#ticket/create'
  695. within :active_content do
  696. find('[data-type=email-out]').click
  697. find('[name=title]').fill_in with: 'Title'
  698. find('[name=customer_id_completion]').fill_in with: 'customer@example.com'
  699. find('[name=group_id]').select 'Users'
  700. find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\""
  701. find('.js-submit').click
  702. end
  703. end
  704. def forward
  705. within :active_content do
  706. # binding.pry
  707. wait.until_exists { find('.textBubble-content .richtext-content') }
  708. click '.js-ArticleAction[data-type=emailForward]'
  709. fill_in 'To', with: 'customer@example.com'
  710. find('.js-submit').click
  711. end
  712. end
  713. def images_identical?(image_a, image_b)
  714. return false if image_a.height != image_b.height
  715. return false if image_a.width != image_b.width
  716. image_a.height.times do |y|
  717. image_a.row(y).each_with_index do |pixel, x|
  718. return false if pixel != image_b[x, y]
  719. end
  720. end
  721. true
  722. end
  723. it 'keeps image intact' do
  724. create_ticket
  725. forward
  726. images = current_ticket.articles.map do |article|
  727. ChunkyPNG::Image.from_string article.attachments.first.content
  728. end
  729. expect(images_identical?(images.first, images.second)).to be(true)
  730. end
  731. end
  732. # https://github.com/zammad/zammad/issues/3335
  733. context 'ticket state sort order maintained when locale is de-de', authenticated_as: :authenticate do
  734. def authenticate
  735. user.preferences[:locale] = 'de-de'
  736. user
  737. end
  738. context 'when existing ticket is open' do
  739. let(:user) { create(:customer) }
  740. let(:ticket) { create(:ticket, customer: user) }
  741. it 'shows ticket state dropdown options in sorted translated alphabetically order' do
  742. visit "ticket/zoom/#{ticket.id}"
  743. expect(all('select[name=state_id] option').map(&:text)).to eq(%w[geschlossen neu offen])
  744. end
  745. end
  746. context 'when a new ticket is created' do
  747. let(:user) { create(:agent, groups: [permitted_group]) }
  748. let(:permitted_group) { create(:group) }
  749. it 'shows ticket state dropdown options in sorted order' do
  750. visit 'ticket/create'
  751. expect(all('select[name=state_id] option').map(&:text)).to eq ['-', 'geschlossen', 'neu', 'offen', 'warten auf Erinnerung', 'warten auf schliessen']
  752. end
  753. end
  754. end
  755. context 'object manager attribute permission view' do
  756. let!(:group_users) { Group.find_by(name: 'Users') }
  757. shared_examples 'shows attributes and values for agent view and editable' do
  758. it 'shows attributes and values for agent view and editable', authenticated_as: :current_user do
  759. visit "ticket/zoom/#{ticket.id}"
  760. refresh # refresh to have assets generated for ticket
  761. expect(page).to have_select('state_id', options: ['new', 'open', 'pending reminder', 'pending close', 'closed'])
  762. expect(page).to have_select('priority_id')
  763. expect(page).to have_select('owner_id')
  764. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  765. end
  766. end
  767. shared_examples 'shows attributes and values for agent view but disabled' do
  768. it 'shows attributes and values for agent view but disabled', authenticated_as: :current_user do
  769. visit "ticket/zoom/#{ticket.id}"
  770. refresh # refresh to have assets generated for ticket
  771. expect(page).to have_css('select[name=state_id][disabled]')
  772. expect(page).to have_css('select[name=priority_id][disabled]')
  773. expect(page).to have_css('select[name=owner_id][disabled]')
  774. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  775. end
  776. end
  777. shared_examples 'shows attributes and values for customer view' do
  778. it 'shows attributes and values for customer view', authenticated_as: :current_user do
  779. visit "ticket/zoom/#{ticket.id}"
  780. refresh # refresh to have assets generated for ticket
  781. expect(page).to have_select('state_id', options: %w[new open closed], wait: 10)
  782. expect(page).to have_no_select('priority_id')
  783. expect(page).to have_no_select('owner_id')
  784. expect(page).to have_no_css('div.tabsSidebar-tab[data-tab=customer]')
  785. end
  786. end
  787. context 'as customer' do
  788. let!(:current_user) { create(:customer) }
  789. let(:ticket) { create(:ticket, customer: current_user) }
  790. include_examples 'shows attributes and values for customer view'
  791. end
  792. context 'as agent with full permissions' do
  793. let(:current_user) { create(:agent, groups: [ group_users ]) }
  794. let(:ticket) { create(:ticket, group: group_users) }
  795. include_examples 'shows attributes and values for agent view and editable'
  796. end
  797. context 'as agent with change permissions' do
  798. let!(:current_user) { create(:agent) }
  799. let(:ticket) { create(:ticket, group: group_users) }
  800. before do
  801. current_user.group_names_access_map = {
  802. group_users.name => %w[read change],
  803. }
  804. end
  805. include_examples 'shows attributes and values for agent view and editable'
  806. end
  807. context 'as agent with read permissions' do
  808. let!(:current_user) { create(:agent) }
  809. let(:ticket) { create(:ticket, group: group_users) }
  810. before do
  811. current_user.group_names_access_map = {
  812. group_users.name => 'read',
  813. }
  814. end
  815. include_examples 'shows attributes and values for agent view but disabled'
  816. end
  817. context 'as agent+customer with full permissions' do
  818. let!(:current_user) { create(:agent_and_customer, groups: [ group_users ]) }
  819. context 'normal ticket' do
  820. let(:ticket) { create(:ticket, group: group_users) }
  821. include_examples 'shows attributes and values for agent view and editable'
  822. end
  823. context 'ticket where current_user is also customer' do
  824. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  825. include_examples 'shows attributes and values for agent view and editable'
  826. end
  827. end
  828. context 'as agent+customer with change permissions' do
  829. let!(:current_user) { create(:agent_and_customer) }
  830. before do
  831. current_user.group_names_access_map = {
  832. group_users.name => %w[read change],
  833. }
  834. end
  835. context 'normal ticket' do
  836. let(:ticket) { create(:ticket, group: group_users) }
  837. include_examples 'shows attributes and values for agent view and editable'
  838. end
  839. context 'ticket where current_user is also customer' do
  840. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  841. include_examples 'shows attributes and values for agent view and editable'
  842. end
  843. end
  844. context 'as agent+customer with read permissions' do
  845. let!(:current_user) { create(:agent_and_customer) }
  846. before do
  847. current_user.group_names_access_map = {
  848. group_users.name => 'read',
  849. }
  850. end
  851. context 'normal ticket' do
  852. let(:ticket) { create(:ticket, group: group_users) }
  853. include_examples 'shows attributes and values for agent view but disabled'
  854. end
  855. context 'ticket where current_user is also customer' do
  856. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  857. include_examples 'shows attributes and values for agent view but disabled'
  858. end
  859. end
  860. context 'as agent+customer but only customer for the ticket (no agent access)' do
  861. let!(:current_user) { create(:agent_and_customer) }
  862. let(:ticket) { create(:ticket, customer: current_user) }
  863. include_examples 'shows attributes and values for customer view'
  864. end
  865. end
  866. describe 'note visibility', authenticated_as: :customer do
  867. context 'when logged in as a customer' do
  868. let(:customer) { create(:customer) }
  869. let(:ticket) { create(:ticket, customer: customer) }
  870. let!(:ticket_article) { create(:ticket_article, ticket: ticket) }
  871. let!(:ticket_note) { create(:ticket_article, ticket: ticket, internal: true, type_name: 'note') }
  872. it 'previously created private note is not visible' do
  873. visit "ticket/zoom/#{ticket_article.ticket.id}"
  874. expect(page).to have_no_selector(:active_ticket_article, ticket_note)
  875. end
  876. it 'previously created private note shows up via WS push' do
  877. visit "ticket/zoom/#{ticket_article.ticket.id}"
  878. # make sure ticket is done loading and change will be pushed via WS
  879. find(:active_ticket_article, ticket_article)
  880. ticket_note.update!(internal: false)
  881. expect(page).to have_selector(:active_ticket_article, ticket_note, wait: 10)
  882. end
  883. end
  884. end
  885. # https://github.com/zammad/zammad/issues/3012
  886. describe 'article type selection' do
  887. context 'when logged in as a customer', authenticated_as: :customer do
  888. let(:customer) { create(:customer) }
  889. let(:ticket) { create(:ticket, customer: customer) }
  890. it 'hides button for single choice' do
  891. visit "ticket/zoom/#{ticket.id}"
  892. find('.articleNewEdit-body').send_keys('Some reply')
  893. expect(page).to have_no_selector('.js-selectedArticleType')
  894. end
  895. end
  896. context 'when logged in as an agent' do
  897. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  898. it 'shows button for multiple choices' do
  899. visit "ticket/zoom/#{ticket.id}"
  900. find('.articleNewEdit-body').send_keys('Some reply')
  901. expect(page).to have_selector('.js-selectedArticleType')
  902. end
  903. end
  904. end
  905. # https://github.com/zammad/zammad/issues/3260
  906. describe 'next in overview macro changes URL', authenticated_as: :authenticate do
  907. let(:next_ticket) { create(:ticket, title: 'next Ticket', group: Group.first) }
  908. let(:macro) { create(:macro, name: 'next macro', ux_flow_next_up: 'next_from_overview') }
  909. def authenticate
  910. next_ticket && macro
  911. true
  912. end
  913. it 'to next Ticket ID' do
  914. visit 'ticket/view/all_unassigned'
  915. click_on 'Welcome to Zammad!'
  916. click '.js-openDropdownMacro'
  917. find(:macro, macro.id).click
  918. wait(5, interval: 1).until_constant { current_url }
  919. expect(current_url).to include("ticket/zoom/#{next_ticket.id}")
  920. end
  921. end
  922. # https://github.com/zammad/zammad/issues/3279
  923. describe 'previous/next clickability when at last or first ticket' do
  924. let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) }
  925. let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) }
  926. before do
  927. ticket_a && ticket_b
  928. visit 'ticket/view/all_unassigned'
  929. end
  930. it 'previous is not clickable for the first item' do
  931. open_nth_item(0)
  932. expect { click '.pagination .previous' }.not_to change { page.find('.content.active')[:id] }
  933. end
  934. it 'next is clickable for the first item' do
  935. open_nth_item(0)
  936. expect { click '.pagination .next' }.to change { page.find('.content.active')[:id] }
  937. end
  938. it 'previous is clickable for the middle item' do
  939. open_nth_item(1)
  940. expect { click '.pagination .previous' }.to change { page.find('.content.active')[:id] }
  941. end
  942. it 'next is clickable for the middle item' do
  943. open_nth_item(1)
  944. expect { click '.pagination .next' }.to change { page.find('.content.active')[:id] }
  945. end
  946. it 'previous is clickable for the last item' do
  947. open_nth_item(2)
  948. expect { click '.pagination .previous' }.to change { page.find('.content.active')[:id] }
  949. end
  950. it 'next is not clickable for the last item' do
  951. open_nth_item(2)
  952. expect { click '.pagination .next' }.not_to change { page.find('.content.active')[:id] }
  953. end
  954. def open_nth_item(nth)
  955. within :active_content do
  956. find_all('.table tr.item a[href^="#ticket/zoom"]')[nth].click
  957. end
  958. await_empty_ajax_queue
  959. end
  960. end
  961. # https://github.com/zammad/zammad/issues/3267
  962. describe 'previous/next buttons are added when open ticket is opened from overview' do
  963. let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) }
  964. let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) }
  965. # prepare an opened ticket and go to overview
  966. before do
  967. ticket_a && ticket_b
  968. visit "ticket/zoom/#{ticket_a.id}"
  969. visit 'ticket/view/all_unassigned'
  970. end
  971. it 'adds previous/next buttons to existing ticket' do
  972. within :active_content do
  973. click_on ticket_a.title
  974. expect(page).to have_css('.pagination-counter', wait: 10)
  975. end
  976. end
  977. it 'keeps previous/next buttons when navigating to overview ticket from elsewhere' do
  978. within :active_content do
  979. click_on ticket_a.title
  980. visit 'dashboard'
  981. visit "ticket/zoom/#{ticket_a.id}"
  982. expect(page).to have_css('.pagination-counter', wait: 10)
  983. end
  984. end
  985. end
  986. # https://github.com/zammad/zammad/issues/2942
  987. describe 'attachments are lost in specific conditions' do
  988. let(:ticket) { create(:ticket, group: Group.first) }
  989. it 'attachment is retained when forwarding a fresh article' do
  990. ensure_websocket do
  991. visit "ticket/zoom/#{ticket.id}"
  992. end
  993. # add an article, forcing reset of form_id
  994. # click in the upper most upper left corner of the article create textbox
  995. # (that works for both Firefox and Chrome)
  996. # to avoid clicking on attachment upload
  997. find('.js-writeArea').click({ x: 5, y: 5 })
  998. # wait for propagateOpenTextarea to be completed
  999. find('.attachmentPlaceholder-label').in_fixed_position
  1000. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  1001. # write article content
  1002. find('.articleNewEdit-body').send_keys('Some reply')
  1003. click '.js-submit'
  1004. # wait for article to be added to the page
  1005. expect(page).to have_css('.ticket-article-item', count: 1)
  1006. # create a on-the-fly article with attachment that will get pushed to open browser
  1007. article1 = create(:ticket_article, ticket: ticket)
  1008. Store.add(
  1009. object: 'Ticket::Article',
  1010. o_id: article1.id,
  1011. data: 'some content',
  1012. filename: 'some_file.txt',
  1013. preferences: {
  1014. 'Content-Type' => 'text/plain',
  1015. },
  1016. created_by_id: 1,
  1017. )
  1018. # wait for article to be added to the page
  1019. expect(page).to have_css('.ticket-article-item', count: 2, wait: 10)
  1020. # click on forward of created article
  1021. within :active_ticket_article, article1 do
  1022. find('a[data-type=emailForward]').click
  1023. end
  1024. # wait for propagateOpenTextarea to be completed
  1025. find('.attachmentPlaceholder-label').in_fixed_position
  1026. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  1027. # fill forward information and create article
  1028. fill_in 'To', with: 'forward@example.org'
  1029. find('.articleNewEdit-body').send_keys('Forwarding with the attachment')
  1030. click '.js-submit'
  1031. # wait for article to be added to the page
  1032. expect(page).to have_css('.ticket-article-item', count: 3)
  1033. # check if attachment was forwarded successfully
  1034. within :active_ticket_article, ticket.reload.articles.last do
  1035. within '.attachments--list' do
  1036. expect(page).to have_text('some_file.txt')
  1037. end
  1038. end
  1039. end
  1040. end
  1041. describe 'mentions' do
  1042. context 'when logged in as agent' do
  1043. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1044. let!(:other_agent) { create(:agent, groups: [Group.find_by(name: 'Users')]) }
  1045. it 'can subscribe and unsubscribe' do
  1046. ensure_websocket do
  1047. visit "ticket/zoom/#{ticket.id}"
  1048. click '.js-subscriptions .js-subscribe input'
  1049. expect(page).to have_selector('.js-subscriptions .js-unsubscribe input', wait: 10)
  1050. expect(page).to have_selector('.js-subscriptions span.avatar', wait: 10)
  1051. click '.js-subscriptions .js-unsubscribe input'
  1052. expect(page).to have_selector('.js-subscriptions .js-subscribe input', wait: 10)
  1053. expect(page).to have_no_selector('.js-subscriptions span.avatar', wait: 10)
  1054. create(:mention, mentionable: ticket, user: other_agent)
  1055. expect(page).to have_selector('.js-subscriptions span.avatar', wait: 10)
  1056. # check history for mention entries
  1057. click 'h2.sidebar-header-headline.js-headline'
  1058. click 'li[data-type=ticket-history] a'
  1059. expect(page).to have_text('created Mention', wait: 10)
  1060. expect(page).to have_text('removed Mention', wait: 10)
  1061. end
  1062. end
  1063. end
  1064. end
  1065. # https://github.com/zammad/zammad/issues/2671
  1066. describe 'Pending time field in ticket sidebar', authenticated_as: :customer do
  1067. let(:customer) { create(:customer) }
  1068. let(:ticket) { create(:ticket, customer: customer, pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder')) }
  1069. it 'not shown to customer' do
  1070. visit "ticket/zoom/#{ticket.id}"
  1071. within :active_content do
  1072. expect(page).to have_no_css('.controls[data-name=pending_time]')
  1073. end
  1074. end
  1075. end
  1076. describe 'Pending time field in ticket sidebar as agent' do
  1077. before do
  1078. ticket.update(pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder'))
  1079. visit "ticket/zoom/#{ticket.id}"
  1080. end
  1081. let(:ticket) { Ticket.first }
  1082. let(:elem) { find('.js-timepicker') }
  1083. # has to run asynchronously to keep both Firefox and Safari
  1084. # https://github.com/zammad/zammad/issues/3414
  1085. # https://github.com/zammad/zammad/issues/2887
  1086. context 'when clicking timepicker component' do
  1087. it 'in the first half, hours selected' do
  1088. within :active_content do
  1089. elem.click({ x: 10, y: 20 })
  1090. expect(elem).to have_selection(0..2)
  1091. end
  1092. end
  1093. it 'in the second half, minutes selected' do
  1094. within :active_content do
  1095. elem.click({ x: 35, y: 20 })
  1096. expect(elem).to have_selection(3..5)
  1097. end
  1098. end
  1099. end
  1100. matcher :have_selection do
  1101. match { starts_at == expected.begin && ends_at == expected.end }
  1102. def starts_at
  1103. actual.evaluate_script 'this.selectionStart'
  1104. end
  1105. def ends_at
  1106. actual.evaluate_script 'this.selectionEnd'
  1107. end
  1108. end
  1109. end
  1110. describe 'Article ID URL / link' do
  1111. let(:ticket) { create(:ticket, group: Group.first) }
  1112. let!(:article) { create(:'ticket/article', ticket: ticket) }
  1113. it 'shows Article direct link' do
  1114. ensure_websocket do
  1115. visit "ticket/zoom/#{ticket.id}"
  1116. end
  1117. url = "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/#ticket/zoom/#{ticket.id}/#{article.id}"
  1118. within :active_ticket_article, article do
  1119. expect(page).to have_css(%(a[href="#{url}"]))
  1120. end
  1121. end
  1122. context 'when multiple Articles are present' do
  1123. let(:article_count) { 20 }
  1124. let(:article_top) { ticket.articles.second }
  1125. let(:article_middle) { ticket.articles[ article_count / 2 ] }
  1126. let(:article_bottom) { ticket.articles.last }
  1127. before do
  1128. article_count.times do
  1129. create(:'ticket/article', ticket: ticket, body: SecureRandom.uuid)
  1130. end
  1131. visit "ticket/zoom/#{ticket.id}"
  1132. end
  1133. def wait_for_scroll
  1134. wait(5, interval: 0.2).until_constant do
  1135. find('.ticketZoom').native.location.y
  1136. end
  1137. end
  1138. def check_shown(top: false, middle: false, bottom: false)
  1139. wait_for_scroll
  1140. expect(page).to have_css("div#article-content-#{article_top.id} .richtext-content", obscured: !top)
  1141. .and(have_css("div#article-content-#{article_middle.id} .richtext-content", obscured: !middle, wait: 0))
  1142. .and(have_css("div#article-content-#{article_bottom.id} .richtext-content", obscured: !bottom, wait: 0))
  1143. end
  1144. it 'scrolls to top article ID' do
  1145. visit "ticket/zoom/#{ticket.id}/#{article_top.id}"
  1146. check_shown(top: true)
  1147. end
  1148. it 'scrolls to middle article ID' do
  1149. visit "ticket/zoom/#{ticket.id}/#{article_middle.id}"
  1150. check_shown(middle: true)
  1151. end
  1152. it 'scrolls to bottom article ID' do
  1153. visit "ticket/zoom/#{ticket.id}/#{article_top.id}"
  1154. wait_for_scroll
  1155. visit "ticket/zoom/#{ticket.id}/#{article_bottom.id}"
  1156. check_shown(bottom: true)
  1157. end
  1158. end
  1159. context 'when long articles are present' do
  1160. it 'will properly show the "See more" link if you switch between the ticket and the dashboard on new articles' do
  1161. ensure_websocket do
  1162. # prerender ticket
  1163. visit "ticket/zoom/#{ticket.id}"
  1164. # ticket tab becomes background
  1165. visit 'dashboard'
  1166. end
  1167. # create a new article
  1168. article_id = create(:'ticket/article', ticket: ticket, body: "#{SecureRandom.uuid} #{"lorem ipsum\n" * 200}")
  1169. wait(30).until { has_css?('div.tasks a.is-modified') }
  1170. visit "ticket/zoom/#{ticket.id}"
  1171. within :active_content do
  1172. expect(find("div#article-content-#{article_id.id}")).to have_text('See more')
  1173. end
  1174. end
  1175. end
  1176. end
  1177. describe 'Macros', authenticated_as: :authenticate do
  1178. let(:macro_body) { 'macro <b>body</b>' }
  1179. let(:macro) { create :macro, perform: { 'article.note' => { 'body' => macro_body, 'internal' => 'true', 'subject' => 'macro note' } } }
  1180. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1181. def authenticate
  1182. macro
  1183. true
  1184. end
  1185. it 'does html macro by default' do
  1186. visit "ticket/zoom/#{ticket.id}"
  1187. find('.js-openDropdownMacro').click
  1188. find(:macro, macro.id).click
  1189. expect(ticket.reload.articles.last.body).to eq(macro_body)
  1190. expect(ticket.reload.articles.last.content_type).to eq('text/html')
  1191. end
  1192. end
  1193. describe 'object manager attributes maxlength', authenticated_as: :authenticate, db_strategy: :reset do
  1194. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1195. def authenticate
  1196. ticket
  1197. create :object_manager_attribute_text, name: 'maxtest', display: 'maxtest', screens: attributes_for(:required_screen), data_option: {
  1198. 'type' => 'text',
  1199. 'maxlength' => 3,
  1200. 'null' => true,
  1201. 'translate' => false,
  1202. 'default' => '',
  1203. 'options' => {},
  1204. 'relation' => '',
  1205. }
  1206. ObjectManager::Attribute.migration_execute
  1207. true
  1208. end
  1209. it 'checks ticket zoom' do
  1210. visit "ticket/zoom/#{ticket.id}"
  1211. within(:active_content) do
  1212. fill_in 'maxtest', with: 'hellu'
  1213. expect(page.find_field('maxtest').value).to eq('hel')
  1214. end
  1215. end
  1216. end
  1217. describe 'GitLab Integration', :integration, authenticated_as: :authenticate, required_envs: %w[GITLAB_ENDPOINT GITLAB_APITOKEN] do
  1218. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1219. def authenticate
  1220. Setting.set('gitlab_integration', true)
  1221. Setting.set('gitlab_config', {
  1222. api_token: ENV['GITLAB_APITOKEN'],
  1223. endpoint: ENV['GITLAB_ENDPOINT'],
  1224. })
  1225. true
  1226. end
  1227. it 'creates links and removes them' do
  1228. visit "#ticket/zoom/#{ticket.id}"
  1229. within(:active_content) do
  1230. # switch to GitLab sidebar
  1231. click('.tabsSidebar-tab[data-tab=gitlab]')
  1232. click('.sidebar-header-headline.js-headline')
  1233. # add issue
  1234. click_on 'Link issue'
  1235. fill_in 'link', with: ENV['GITLAB_ISSUE_LINK']
  1236. click_on 'Submit'
  1237. # verify issue
  1238. content = find('.sidebar-git-issue-content')
  1239. expect(content).to have_text('#1 Example issue')
  1240. expect(content).to have_text('critical')
  1241. expect(content).to have_text('special')
  1242. expect(content).to have_text('important milestone')
  1243. expect(content).to have_text('zammad-robot')
  1244. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to eq(ENV['GITLAB_ISSUE_LINK'])
  1245. # check sidebar counter increased to 1
  1246. expect(find('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')).to have_text('1')
  1247. # delete issue
  1248. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITLAB_ISSUE_LINK']}']")
  1249. content = find('.sidebar[data-tab=gitlab] .sidebar-content')
  1250. expect(content).to have_text('No linked issues')
  1251. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to be nil
  1252. # check that counter got removed
  1253. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')
  1254. end
  1255. end
  1256. end
  1257. describe 'GitHub Integration', :integration, authenticated_as: :authenticate, required_envs: %w[GITHUB_ENDPOINT GITHUB_APITOKEN] do
  1258. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1259. def authenticate
  1260. Setting.set('github_integration', true)
  1261. Setting.set('github_config', {
  1262. api_token: ENV['GITHUB_APITOKEN'],
  1263. endpoint: ENV['GITHUB_ENDPOINT'],
  1264. })
  1265. true
  1266. end
  1267. it 'creates links and removes them' do
  1268. visit "#ticket/zoom/#{ticket.id}"
  1269. within(:active_content) do
  1270. # switch to GitHub sidebar
  1271. click('.tabsSidebar-tab[data-tab=github]')
  1272. click('.sidebar-header-headline.js-headline')
  1273. # add issue
  1274. click_on 'Link issue'
  1275. fill_in 'link', with: ENV['GITHUB_ISSUE_LINK']
  1276. click_on 'Submit'
  1277. # verify issue
  1278. content = find('.sidebar-git-issue-content')
  1279. expect(content).to have_text('#1575 GitHub integration')
  1280. expect(content).to have_text('feature backlog')
  1281. expect(content).to have_text('integration')
  1282. expect(content).to have_text('4.0')
  1283. expect(content).to have_text('Thorsten')
  1284. expect(ticket.reload.preferences[:github][:issue_links][0]).to eq(ENV['GITHUB_ISSUE_LINK'])
  1285. # check sidebar counter increased to 1
  1286. expect(find('.tabsSidebar-tab[data-tab=github] .js-tabCounter')).to have_text('1')
  1287. # delete issue
  1288. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITHUB_ISSUE_LINK']}']")
  1289. content = find('.sidebar[data-tab=github] .sidebar-content')
  1290. expect(content).to have_text('No linked issues')
  1291. expect(ticket.reload.preferences[:github][:issue_links][0]).to be nil
  1292. # check that counter got removed
  1293. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=github] .js-tabCounter')
  1294. end
  1295. end
  1296. end
  1297. describe 'Core Workflow' do
  1298. include_examples 'core workflow' do
  1299. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1300. let(:object_name) { 'Ticket' }
  1301. let(:before_it) do
  1302. lambda {
  1303. ensure_websocket(check_if_pinged: false) do
  1304. visit "#ticket/zoom/#{ticket.id}"
  1305. end
  1306. }
  1307. end
  1308. end
  1309. end
  1310. context 'Sidebar - Open & Closed Tickets', searchindex: true, authenticated_as: :authenticate do
  1311. let(:customer) { create(:customer, :with_org) }
  1312. let(:ticket_open) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, title: SecureRandom.uuid) }
  1313. let(:ticket_closed) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, state: Ticket::State.find_by(name: 'closed'), title: SecureRandom.uuid) }
  1314. def authenticate
  1315. ticket_open
  1316. ticket_closed
  1317. configure_elasticsearch(required: true, rebuild: true)
  1318. Scheduler.worker(true)
  1319. true
  1320. end
  1321. it 'does show open and closed tickets in advanced search url' do
  1322. visit "#ticket/zoom/#{ticket_open.id}"
  1323. click '.tabsSidebar-tab[data-tab=customer]'
  1324. click '.user-tickets[data-type=open]'
  1325. expect(page).to have_text(ticket_open.title, wait: 20)
  1326. visit "#ticket/zoom/#{ticket_open.id}"
  1327. click '.user-tickets[data-type=closed]'
  1328. expect(page).to have_text(ticket_closed.title, wait: 20)
  1329. end
  1330. end
  1331. context 'Sidebar - Organization' do
  1332. let(:organization) { create(:organization) }
  1333. context 'members section' do
  1334. let(:customers) { create_list(:customer, 50, organization: organization) }
  1335. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customers.first) }
  1336. let(:members) { organization.members.order(id: :asc) }
  1337. before do
  1338. visit "#ticket/zoom/#{ticket.id}"
  1339. click '.tabsSidebar-tab[data-tab=organization]'
  1340. end
  1341. it 'shows first 10 members and loads more on demand' do
  1342. expect(page).to have_text(members[9].fullname)
  1343. expect(page).to have_no_text(members[10].fullname)
  1344. click '.js-showMoreMembers'
  1345. expect(page).to have_text(members[10].fullname)
  1346. end
  1347. end
  1348. end
  1349. describe 'merging', authenticated_as: :user do
  1350. before do
  1351. merged_into_trigger && received_merge_trigger && update_trigger
  1352. visit "ticket/zoom/#{ticket.id}"
  1353. visit "ticket/zoom/#{target_ticket.id}"
  1354. ensure_websocket do
  1355. visit 'dashboard'
  1356. end
  1357. end
  1358. let(:merged_into_trigger) { create(:trigger, :conditionable, condition_ticket_action: :merged_into) }
  1359. let(:received_merge_trigger) { create(:trigger, :conditionable, condition_ticket_action: :received_merge) }
  1360. let(:update_trigger) { create(:trigger, :conditionable, condition_ticket_action: :update) }
  1361. let(:ticket) { create(:ticket) }
  1362. let(:target_ticket) { create(:ticket) }
  1363. let(:user) { create(:agent, :preferencable, notification_group_ids: [ticket, target_ticket].map(&:group_id), groups: [ticket, target_ticket].map(&:group)) }
  1364. context 'when merging ticket' do
  1365. before do
  1366. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  1367. end
  1368. it 'pulses source ticket' do
  1369. expect(page).to have_css("#navigation a.is-modified[data-key=\"Ticket-#{ticket.id}\"]")
  1370. end
  1371. it 'pulses target ticket' do
  1372. expect(page).to have_css("#navigation a.is-modified[data-key=\"Ticket-#{target_ticket.id}\"]")
  1373. end
  1374. end
  1375. context 'when merging and looking at online notifications', :performs_jobs do
  1376. before do
  1377. perform_enqueued_jobs do
  1378. ticket.merge_to(ticket_id: target_ticket.id, user_id: 1)
  1379. end
  1380. find('.js-toggleNotifications').click
  1381. end
  1382. it 'shows online notification for source ticket' do
  1383. expect(page).to have_text("Ticket #{ticket.title} was merged into another ticket")
  1384. end
  1385. it 'shows online notification for target ticket' do
  1386. expect(page).to have_text("Another ticket was merged into ticket #{ticket.title}")
  1387. end
  1388. end
  1389. end
  1390. describe 'Tab behaviour - Define default "stay on tab" / "close tab" behavior #257', authenticated_as: :authenticate do
  1391. def authenticate
  1392. Setting.set('ticket_secondary_action', 'closeTabOnTicketClose')
  1393. true
  1394. end
  1395. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1396. before do
  1397. visit "ticket/zoom/#{ticket.id}"
  1398. end
  1399. it 'does show the default of the system' do
  1400. expect(page).to have_text('Close tab on ticket close')
  1401. end
  1402. it 'does save state for the user preferences' do
  1403. click '.js-attributeBar .dropup div'
  1404. click 'span[data-type=stayOnTab]'
  1405. refresh
  1406. expect(page).to have_text('Stay on tab')
  1407. expect(User.find_by(email: 'admin@example.com').preferences[:secondaryAction]).to eq('stayOnTab')
  1408. end
  1409. context 'Tab behaviour - Close tab on ticket close' do
  1410. it 'does not close the tab without any action' do
  1411. click '.js-submit'
  1412. expect(current_url).to include('ticket/zoom')
  1413. end
  1414. it 'does close the tab on ticket close' do
  1415. select 'closed', from: 'State'
  1416. click '.js-submit'
  1417. expect(current_url).not_to include('ticket/zoom')
  1418. end
  1419. end
  1420. context 'Tab behaviour - Stay on tab' do
  1421. def authenticate
  1422. Setting.set('ticket_secondary_action', 'stayOnTab')
  1423. true
  1424. end
  1425. it 'does not close the tab without any action' do
  1426. click '.js-submit'
  1427. expect(current_url).to include('ticket/zoom')
  1428. end
  1429. it 'does not close the tab on ticket close' do
  1430. select 'closed', from: 'State'
  1431. click '.js-submit'
  1432. expect(current_url).to include('ticket/zoom')
  1433. end
  1434. end
  1435. context 'Tab behaviour - Close tab' do
  1436. def authenticate
  1437. Setting.set('ticket_secondary_action', 'closeTab')
  1438. true
  1439. end
  1440. it 'does close the tab without any action' do
  1441. click '.js-submit'
  1442. expect(current_url).not_to include('ticket/zoom')
  1443. end
  1444. it 'does close the tab on ticket close' do
  1445. select 'closed', from: 'State'
  1446. click '.js-submit'
  1447. expect(current_url).not_to include('ticket/zoom')
  1448. end
  1449. end
  1450. context 'Tab behaviour - Next in overview' do
  1451. let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1452. let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1453. let(:ticket3) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1454. def authenticate
  1455. Setting.set('ticket_secondary_action', 'closeNextInOverview')
  1456. ticket1
  1457. ticket2
  1458. ticket3
  1459. true
  1460. end
  1461. before do
  1462. visit 'ticket/view/all_open'
  1463. end
  1464. it 'does change the tab without any action' do
  1465. click_on ticket1.title
  1466. expect(current_url).to include("ticket/zoom/#{ticket1.id}")
  1467. click '.js-submit'
  1468. expect(current_url).to include("ticket/zoom/#{ticket2.id}")
  1469. click '.js-submit'
  1470. expect(current_url).to include("ticket/zoom/#{ticket3.id}")
  1471. end
  1472. it 'does show default stay on tab if secondary action is not given' do
  1473. click_on ticket1.title
  1474. refresh
  1475. expect(page).to have_text('Stay on tab')
  1476. end
  1477. end
  1478. context 'On ticket switch' do
  1479. let(:ticket1) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1480. let(:ticket2) { create(:ticket, title: SecureRandom.uuid, group: Group.find_by(name: 'Users')) }
  1481. before do
  1482. visit "ticket/zoom/#{ticket1.id}"
  1483. visit "ticket/zoom/#{ticket2.id}"
  1484. end
  1485. it 'does setup the last behaviour' do
  1486. click '.js-attributeBar .dropup div'
  1487. click 'span[data-type=stayOnTab]'
  1488. visit "ticket/zoom/#{ticket1.id}"
  1489. expect(page).to have_text('Stay on tab')
  1490. end
  1491. end
  1492. end
  1493. describe 'Core Workflow: Show hidden attributes on group selection (ticket edit) #3739', authenticated_as: :authenticate, db_strategy: :reset do
  1494. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1495. let(:field_name) { SecureRandom.uuid }
  1496. let(:field) do
  1497. create :object_manager_attribute_text, name: field_name, display: field_name, screens: {
  1498. 'edit' => {
  1499. 'ticket.agent' => {
  1500. 'shown' => false,
  1501. 'required' => false,
  1502. }
  1503. }
  1504. }
  1505. ObjectManager::Attribute.migration_execute
  1506. end
  1507. before do
  1508. visit "#ticket/zoom/#{ticket.id}"
  1509. end
  1510. context 'when field visible' do
  1511. let(:workflow) do
  1512. create(:core_workflow,
  1513. object: 'Ticket',
  1514. perform: { "ticket.#{field_name}" => { 'operator' => 'show', 'show' => 'true' } })
  1515. end
  1516. def authenticate
  1517. field
  1518. workflow
  1519. true
  1520. end
  1521. it 'does show up the field' do
  1522. expect(page).to have_css("div[data-attribute-name='#{field_name}']")
  1523. end
  1524. end
  1525. context 'when field hidden' do
  1526. def authenticate
  1527. field
  1528. true
  1529. end
  1530. it 'does not show the field' do
  1531. expect(page).to have_css("div[data-attribute-name='#{field_name}'].is-hidden", visible: :hidden)
  1532. end
  1533. end
  1534. end
  1535. describe 'Notes on existing ticks are discarded by editing profile settings #3088' do
  1536. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1537. before do
  1538. visit "#ticket/zoom/#{ticket.id}"
  1539. end
  1540. def upload_and_set_text
  1541. page.find('.js-textarea').send_keys("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1542. page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1543. expect(page).to have_text('mail001.box')
  1544. wait_for_upload_present
  1545. end
  1546. def wait_for_upload_present
  1547. wait(5).until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].present? }
  1548. end
  1549. def wait_for_upload_blank
  1550. wait(5).until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].blank? }
  1551. end
  1552. def switch_language_german
  1553. visit '#profile/language'
  1554. page.find('.js-input').click
  1555. page.find('.js-input').set('Deutsch')
  1556. page.find('.js-input').send_keys(:enter)
  1557. click_on 'Submit'
  1558. visit "#ticket/zoom/#{ticket.id}"
  1559. expect(page).to have_text('Dateien wählen')
  1560. end
  1561. def expect_upload_and_text
  1562. expect(page.find('.article-new')).to have_text('mail001.box')
  1563. expect(page.find('.article-new')).to have_text("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1564. end
  1565. def expect_no_upload_and_text
  1566. expect(page.find('.article-new')).to have_no_text('mail001.box')
  1567. expect(page.find('.article-new')).to have_no_text("Hello\nThis\nis\nimportant!\nyo\nhoho\ntest test test test")
  1568. end
  1569. it 'does show up the attachments after a reload of the page' do
  1570. upload_and_set_text
  1571. expect_upload_and_text
  1572. refresh
  1573. expect_upload_and_text
  1574. end
  1575. it 'does show up the attachments after updating language (ui:rerender event)' do
  1576. upload_and_set_text
  1577. expect_upload_and_text
  1578. switch_language_german
  1579. expect_upload_and_text
  1580. end
  1581. it 'does remove attachments and text on reset' do
  1582. upload_and_set_text
  1583. expect_upload_and_text
  1584. page.find('.js-reset').click
  1585. wait_for_upload_blank
  1586. expect_no_upload_and_text
  1587. refresh
  1588. expect_no_upload_and_text
  1589. end
  1590. context 'when rerendering (#3831)' do
  1591. def rerender
  1592. page.evaluate_script("App.Event.trigger('ui:rerender')")
  1593. end
  1594. it 'does loose attachments after rerender' do
  1595. upload_and_set_text
  1596. expect_upload_and_text
  1597. rerender
  1598. expect_upload_and_text
  1599. end
  1600. it 'does not readd the attachments after reset' do
  1601. upload_and_set_text
  1602. expect_upload_and_text
  1603. page.find('.js-reset').click
  1604. wait_for_upload_blank
  1605. expect_no_upload_and_text
  1606. rerender
  1607. expect_no_upload_and_text
  1608. end
  1609. it 'does not readd the attachments after submit' do
  1610. upload_and_set_text
  1611. expect_upload_and_text
  1612. page.find('.js-submit').click
  1613. wait_for_upload_blank
  1614. expect_no_upload_and_text
  1615. rerender
  1616. expect_no_upload_and_text
  1617. end
  1618. it 'does not show the ticket as changed after the upload removal' do
  1619. page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1620. expect(page.find('.article-new')).to have_text('mail001.box')
  1621. wait_for_upload_present
  1622. begin
  1623. page.evaluate_script("$('div.attachment-delete.js-delete:last').click()") # not interactable
  1624. rescue # Lint/SuppressedException
  1625. # because its not interactable it also
  1626. # returns this weird exception for the jquery
  1627. # even tho it worked fine
  1628. end
  1629. expect(page).to have_no_selector('.js-reset')
  1630. end
  1631. end
  1632. end
  1633. describe 'Unable to close tickets in certran cases if core workflow is used #3710', authenticated_as: :authenticate, db_strategy: :reset do
  1634. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1635. let(:field_name) { SecureRandom.uuid }
  1636. let(:field) do
  1637. create :object_manager_attribute_text, name: field_name, display: field_name, screens: {
  1638. 'edit' => {
  1639. 'ticket.agent' => {
  1640. 'shown' => false,
  1641. 'required' => false,
  1642. }
  1643. }
  1644. }
  1645. ObjectManager::Attribute.migration_execute
  1646. end
  1647. let(:workflow) do
  1648. create(:core_workflow,
  1649. object: 'Ticket',
  1650. perform: { "ticket.#{field_name}" => { 'operator' => 'set_mandatory', 'set_mandatory' => 'true' } })
  1651. end
  1652. def authenticate
  1653. field
  1654. workflow
  1655. true
  1656. end
  1657. before do
  1658. visit "#ticket/zoom/#{ticket.id}"
  1659. end
  1660. it 'does save the ticket because the field is mandatory but hidden' do
  1661. admin = User.find_by(email: 'admin@example.com')
  1662. select admin.fullname, from: 'Owner'
  1663. find('.js-submit').click
  1664. expect(ticket.reload.owner_id).to eq(admin.id)
  1665. end
  1666. end
  1667. describe "escaped 'Set fixed' workflows don't refresh set values on active ticket sessions #3757", authenticated_as: :authenticate, db_strategy: :reset do
  1668. let(:field_name) { SecureRandom.uuid }
  1669. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), field_name => false) }
  1670. def authenticate
  1671. workflow
  1672. create :object_manager_attribute_boolean, name: field_name, display: field_name, screens: attributes_for(:required_screen)
  1673. ObjectManager::Attribute.migration_execute
  1674. ticket
  1675. true
  1676. end
  1677. before do
  1678. visit "#ticket/zoom/#{ticket.id}"
  1679. end
  1680. context 'when operator set_fixed_to' do
  1681. let(:workflow) do
  1682. create(:core_workflow,
  1683. object: 'Ticket',
  1684. perform: { "ticket.#{field_name}" => { 'operator' => 'set_fixed_to', 'set_fixed_to' => ['false'] } })
  1685. end
  1686. context 'when saved value is removed by set_fixed_to operator' do
  1687. it 'does show up the saved value if it would not be possible because of the restriction' do
  1688. expect(page.find("select[name='#{field_name}']").value).to eq('false')
  1689. ticket.update(field_name => true)
  1690. wait(10, interval: 0.5).until { page.find("select[name='#{field_name}']").value == 'true' }
  1691. expect(page.find("select[name='#{field_name}']").value).to eq('true')
  1692. end
  1693. end
  1694. end
  1695. context 'when operator remove_option' do
  1696. let(:workflow) do
  1697. create(:core_workflow,
  1698. object: 'Ticket',
  1699. perform: { "ticket.#{field_name}" => { 'operator' => 'remove_option', 'remove_option' => ['true'] } })
  1700. end
  1701. context 'when saved value is removed by set_fixed_to operator' do
  1702. it 'does show up the saved value if it would not be possible because of the restriction' do
  1703. expect(page.find("select[name='#{field_name}']").value).to eq('false')
  1704. ticket.update(field_name => true)
  1705. wait(10, interval: 0.5).until { page.find("select[name='#{field_name}']").value == 'true' }
  1706. expect(page.find("select[name='#{field_name}']").value).to eq('true')
  1707. end
  1708. end
  1709. end
  1710. end
  1711. context 'Basic sidebar handling because of regressions in #3757' do
  1712. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1713. before do
  1714. visit "#ticket/zoom/#{ticket.id}"
  1715. end
  1716. it 'does show up the new priority' do
  1717. high_prio = Ticket::Priority.find_by(name: '3 high')
  1718. ticket.update(priority: high_prio)
  1719. wait(10, interval: 0.5).until { page.find("select[name='priority_id']").value == high_prio.id.to_s }
  1720. expect(page.find("select[name='priority_id']").value).to eq(high_prio.id.to_s)
  1721. end
  1722. it 'does show up the new group (different case because it will also trigger a full rerender because of potential permission changes)' do
  1723. group = Group.find_by(name: 'some group1')
  1724. ticket.update(group: group)
  1725. wait(10, interval: 0.5).until { page.find("select[name='group_id']").value == group.id.to_s }
  1726. expect(page.find("select[name='group_id']").value).to eq(group.id.to_s)
  1727. end
  1728. it 'does show up the new state and pending time' do
  1729. pending_state = Ticket::State.find_by(name: 'pending reminder')
  1730. ticket.update(state: pending_state, pending_time: 1.day.from_now)
  1731. wait(10, interval: 0.5).until { page.find("select[name='state_id']").value == pending_state.id.to_s }
  1732. expect(page.find("select[name='state_id']").value).to eq(pending_state.id.to_s)
  1733. expect(page).to have_selector("div[data-name='pending_time']")
  1734. end
  1735. it 'does merge attributes with remote priority (ajax) and local state (user)' do
  1736. select 'closed', from: 'State'
  1737. high_prio = Ticket::Priority.find_by(name: '3 high')
  1738. closed_state = Ticket::State.find_by(name: 'closed')
  1739. ticket.update(priority: high_prio)
  1740. wait(10, interval: 0.5).until { page.find("select[name='priority_id']").value == high_prio.id.to_s }
  1741. expect(page.find("select[name='priority_id']").value).to eq(high_prio.id.to_s)
  1742. expect(page.find("select[name='state_id']").value).to eq(closed_state.id.to_s)
  1743. end
  1744. context 'when 2 users are in 2 different tickets' do
  1745. let(:ticket2) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1746. let(:agent2) { create(:agent, password: 'test', groups: [Group.find_by(name: 'Users')]) }
  1747. before do
  1748. using_session(:second_browser) do
  1749. login(
  1750. username: agent2.login,
  1751. password: 'test',
  1752. )
  1753. visit "#ticket/zoom/#{ticket.id}"
  1754. visit "#ticket/zoom/#{ticket2.id}"
  1755. end
  1756. end
  1757. it 'does not make any changes to the second browser ticket' do
  1758. closed_state = Ticket::State.find_by(name: 'closed')
  1759. select 'closed', from: 'State'
  1760. find('.js-submit').click
  1761. using_session(:second_browser) do
  1762. sleep 3
  1763. expect(page.find("select[name='state_id']").value).not_to eq(closed_state.id.to_s)
  1764. end
  1765. end
  1766. end
  1767. end
  1768. context 'Article box opening on tickets with no changes #3789' do
  1769. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1770. before do
  1771. visit "#ticket/zoom/#{ticket.id}"
  1772. end
  1773. it 'does not expand the article box without changes' do
  1774. refresh
  1775. sleep 3
  1776. expect(page).to have_no_selector('form.article-add.is-open')
  1777. end
  1778. it 'does open and close by usage' do
  1779. find('.js-textarea').send_keys(' ')
  1780. expect(page).to have_selector('form.article-add.is-open')
  1781. find('input#global-search').click
  1782. expect(page).to have_no_selector('form.article-add.is-open')
  1783. end
  1784. it 'does open automatically when body is given from sidebar' do
  1785. find('.js-textarea').send_keys('test')
  1786. wait(5).until { Taskbar.find_by(key: "Ticket-#{ticket.id}").state.dig('article', 'body').present? }
  1787. refresh
  1788. expect(page).to have_selector('form.article-add.is-open')
  1789. end
  1790. it 'does open automatically when attachment is given from sidebar' do
  1791. page.find('input#fileUpload_1', visible: :all).set(Rails.root.join('test/data/mail/mail001.box'))
  1792. wait(5).until { Taskbar.find_by(key: "Ticket-#{ticket.id}").attributes_with_association_ids['attachments'].present? }
  1793. refresh
  1794. expect(page).to have_selector('form.article-add.is-open')
  1795. end
  1796. end
  1797. context 'Owner should get cleared if not listed in changed group #3818', authenticated_as: :authenticate do
  1798. let(:group1) { create(:group) }
  1799. let(:group2) { create(:group) }
  1800. let(:agent1) { create(:agent) }
  1801. let(:agent2) { create(:agent) }
  1802. let(:ticket) { create(:ticket, group: group1, owner: agent1) }
  1803. def authenticate
  1804. agent1.group_names_access_map = {
  1805. group1.name => 'full',
  1806. group2.name => %w[read change overview]
  1807. }
  1808. agent2.group_names_access_map = {
  1809. group1.name => 'full',
  1810. group2.name => 'full',
  1811. }
  1812. agent1
  1813. end
  1814. before do
  1815. visit "#ticket/zoom/#{ticket.id}"
  1816. end
  1817. it 'does clear agent1 on select of group 2' do
  1818. select group2.name, from: 'Group'
  1819. wait(5).until { page.find('select[name=owner_id]').value != agent1.id.to_s }
  1820. expect(page.find('select[name=owner_id]').value).to eq('')
  1821. expect(page.all('select[name=owner_id] option').map(&:value)).not_to include(agent1.id.to_s)
  1822. expect(page.all('select[name=owner_id] option').map(&:value)).to include(agent2.id.to_s)
  1823. end
  1824. end
  1825. describe 'Not displayed fields should not impact the edit screen #3819', authenticated_as: :authenticate, db_strategy: :reset do
  1826. let(:field_name) { SecureRandom.uuid }
  1827. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1828. def authenticate
  1829. create :object_manager_attribute_boolean, default: nil, screens: {
  1830. edit: {
  1831. 'ticket.agent' => {
  1832. shown: false,
  1833. required: false,
  1834. }
  1835. }
  1836. }
  1837. ObjectManager::Attribute.migration_execute
  1838. ticket
  1839. true
  1840. end
  1841. before do
  1842. visit "#ticket/zoom/#{ticket.id}"
  1843. end
  1844. it 'does not show any changes for the field because it has no value and because it is not shown it should also not show the ticket as changed' do
  1845. sleep 3
  1846. expect(page).to have_no_selector('.js-reset')
  1847. end
  1848. end
  1849. end