zoom_spec.rb 86 KB

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