zoom_spec.rb 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713
  1. require 'rails_helper'
  2. RSpec.describe 'Ticket zoom', type: :system do
  3. describe 'owner auto-assignment', authenticated_as: :authenticate do
  4. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users'), state: Ticket::State.find_by(name: 'new')) }
  5. let!(:session_user) { User.find_by(login: 'master@example.com') }
  6. context 'for agent disabled' do
  7. def authenticate
  8. Setting.set('ticket_auto_assignment', false)
  9. Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } })
  10. Setting.set('ticket_auto_assignment_user_ids_ignore', [])
  11. true
  12. end
  13. it 'do not assign ticket to current session user' do
  14. visit "#ticket/zoom/#{ticket.id}"
  15. within(:active_content) do
  16. expect(page).to have_css('select[name=owner_id]')
  17. expect(page).to have_select('owner_id',
  18. selected: '-',
  19. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  20. end
  21. end
  22. end
  23. context 'for agent enabled' do
  24. def authenticate
  25. Setting.set('ticket_auto_assignment', true)
  26. Setting.set('ticket_auto_assignment_selector', { condition: { 'ticket.state_id' => { operator: 'is', value: Ticket::State.by_category(:work_on).pluck(:id) } } })
  27. Setting.set('ticket_auto_assignment_user_ids_ignore', setting_user_ids_ignore) if defined?(setting_user_ids_ignore)
  28. true
  29. end
  30. context 'with empty "ticket_auto_assignment_user_ids_ignore"' do
  31. it 'assigns ticket to current session user' do
  32. visit "#ticket/zoom/#{ticket.id}"
  33. within(:active_content) do
  34. expect(page).to have_css('.content.active select[name=owner_id]')
  35. expect(page).to have_select('owner_id',
  36. selected: session_user.fullname,
  37. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  38. end
  39. end
  40. end
  41. context 'with "ticket_auto_assignment_user_ids_ignore" (as integer)' do
  42. let(:setting_user_ids_ignore) { session_user.id }
  43. it 'assigns ticket not to current session user' do
  44. visit "#ticket/zoom/#{ticket.id}"
  45. within(:active_content) do
  46. expect(page).to have_css('select[name=owner_id]')
  47. expect(page).to have_select('owner_id',
  48. selected: '-',
  49. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  50. end
  51. end
  52. end
  53. context 'with "ticket_auto_assignment_user_ids_ignore" (as string)' do
  54. let(:setting_user_ids_ignore) { session_user.id.to_s }
  55. it 'assigns ticket not to current session user' do
  56. visit "#ticket/zoom/#{ticket.id}"
  57. within(:active_content) do
  58. expect(page).to have_css('select[name=owner_id]')
  59. expect(page).to have_select('owner_id',
  60. selected: '-',
  61. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  62. end
  63. end
  64. end
  65. context 'with "ticket_auto_assignment_user_ids_ignore" (as [integer])' do
  66. let(:setting_user_ids_ignore) { [session_user.id] }
  67. it 'assigns ticket not to current session user' do
  68. visit "#ticket/zoom/#{ticket.id}"
  69. within(:active_content) do
  70. expect(page).to have_css('select[name=owner_id]')
  71. expect(page).to have_select('owner_id',
  72. selected: '-',
  73. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  74. end
  75. end
  76. end
  77. context 'with "ticket_auto_assignment_user_ids_ignore" (as [string])' do
  78. let(:setting_user_ids_ignore) { [session_user.id.to_s] }
  79. it 'assigns ticket not to current session user' do
  80. visit "#ticket/zoom/#{ticket.id}"
  81. within(:active_content) do
  82. expect(page).to have_css('select[name=owner_id]')
  83. expect(page).to have_select('owner_id',
  84. selected: '-',
  85. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  86. end
  87. end
  88. end
  89. context 'with "ticket_auto_assignment_user_ids_ignore" and other user ids' do
  90. let(:setting_user_ids_ignore) { [99_999, 999_999] }
  91. it 'assigns ticket to current session user' do
  92. visit "#ticket/zoom/#{ticket.id}"
  93. within(:active_content) do
  94. expect(page).to have_css('select[name=owner_id]')
  95. expect(page).to have_select('owner_id',
  96. selected: session_user.fullname,
  97. options: ['-', 'Agent 1 Test', 'Test Master Agent'])
  98. end
  99. end
  100. end
  101. end
  102. end
  103. context 'when ticket has an attachment' do
  104. let(:group) { Group.find_by(name: 'Users') }
  105. let(:ticket) { create(:ticket, group: group) }
  106. let(:article) { create(:ticket_article, ticket: ticket) }
  107. let(:attachment_name) { 'some_file.txt' }
  108. before do
  109. Store.add(
  110. object: 'Ticket::Article',
  111. o_id: article.id,
  112. data: 'some content',
  113. filename: attachment_name,
  114. preferences: {
  115. 'Content-Type' => 'text/plain',
  116. },
  117. created_by_id: 1,
  118. )
  119. end
  120. context 'article was already forwarded once' do
  121. before do
  122. visit "#ticket/zoom/#{ticket.id}"
  123. within(:active_content) do
  124. find('a[data-type=emailForward]').click
  125. click('.js-reset')
  126. have_no_css('.js-reset')
  127. end
  128. end
  129. it 'adds attachments when forwarding multiple times' do
  130. within(:active_content) do
  131. find('a[data-type=emailForward]').click
  132. end
  133. within('.js-writeArea') do
  134. expect(page).to have_text attachment_name
  135. end
  136. end
  137. end
  138. end
  139. context 'replying' do
  140. context 'Group without signature' do
  141. let(:ticket) { create(:ticket) }
  142. let(:current_user) { create(:agent, password: 'test', groups: [ticket.group]) }
  143. before do
  144. # initial article to reply to
  145. create(:ticket_article, ticket: ticket)
  146. end
  147. it 'ensures that text input opens on multiple replies', authenticated_as: :current_user do
  148. visit "ticket/zoom/#{ticket.id}"
  149. 2.times do |article_offset|
  150. articles_existing = 1
  151. articles_expected = articles_existing + (article_offset + 1)
  152. all('a[data-type=emailReply]').last.click
  153. # wait till input box expands completely
  154. find('.attachmentPlaceholder-label').in_fixed_position
  155. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  156. find('.articleNewEdit-body').send_keys('Some reply')
  157. click '.js-submit'
  158. expect(page).to have_css('.ticket-article-item', count: articles_expected)
  159. end
  160. end
  161. end
  162. context 'to inbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do
  163. let(:agent) { create(:agent, groups: [Group.first]) }
  164. let(:customer) { create(:agent) }
  165. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  166. let!(:article) { create(:ticket_article, :inbound_phone, ticket: ticket) }
  167. it 'goes to customer email' do
  168. visit "ticket/zoom/#{ticket.id}"
  169. within :active_ticket_article, article do
  170. click '.js-ArticleAction[data-type=emailReply]'
  171. end
  172. within :active_content do
  173. within '.article-new' do
  174. expect(find('[name=to]', visible: :all).value).to eq customer.email
  175. end
  176. end
  177. end
  178. end
  179. context 'to outbound phone call', current_user_id: -> { agent.id }, authenticated_as: -> { agent } do
  180. let(:agent) { create(:agent, groups: [Group.first]) }
  181. let(:customer) { create(:agent) }
  182. let(:ticket) { create(:ticket, customer: customer, group: agent.groups.first) }
  183. let!(:article) { create(:ticket_article, :outbound_phone, ticket: ticket) }
  184. it 'goes to customer email' do
  185. visit "ticket/zoom/#{ticket.id}"
  186. within :active_ticket_article, article do
  187. click '.js-ArticleAction[data-type=emailReply]'
  188. end
  189. within :active_content do
  190. within '.article-new' do
  191. expect(find('[name=to]', visible: :all).value).to eq customer.email
  192. end
  193. end
  194. end
  195. end
  196. end
  197. describe 'delete article', authenticated_as: :authenticate do
  198. let(:group) { Group.first }
  199. let(:admin) { create :admin, groups: [group] }
  200. let(:agent) { create :agent, groups: [group] }
  201. let(:other_agent) { create :agent, groups: [group] }
  202. let(:customer) { create :customer }
  203. let(:article) { send(item) }
  204. def authenticate
  205. Setting.set('ui_ticket_zoom_article_delete_timeframe', setting_delete_timeframe) if defined?(setting_delete_timeframe)
  206. article
  207. user
  208. end
  209. def article_communication
  210. create_ticket_article(sender_name: 'Agent', internal: false, type_name: 'email', updated_by: customer)
  211. end
  212. def article_note_self
  213. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: user)
  214. end
  215. def article_note_other
  216. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: other_agent)
  217. end
  218. def article_note_customer
  219. create_ticket_article(sender_name: 'Customer', internal: false, type_name: 'note', updated_by: customer)
  220. end
  221. def article_note_communication_self
  222. create(:ticket_article_type, name: 'note_communication', communication: true)
  223. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: user)
  224. end
  225. def article_note_communication_other
  226. create(:ticket_article_type, name: 'note_communication', communication: true)
  227. create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: other_agent)
  228. end
  229. def create_ticket_article(sender_name:, internal:, type_name:, updated_by:)
  230. UserInfo.current_user_id = updated_by.id
  231. ticket = create :ticket, group: group, customer: customer
  232. create(:ticket_article,
  233. sender_name: sender_name, internal: internal, type_name: type_name, ticket: ticket,
  234. body: "to be deleted #{offset} #{item}",
  235. created_at: offset.ago, updated_at: offset.ago)
  236. end
  237. context 'going through full stack' do
  238. context 'as admin' do
  239. let(:user) { admin }
  240. let(:item) { 'article_note_self' }
  241. let(:offset) { 0.minutes }
  242. it 'succeeds' do
  243. ensure_websocket do
  244. visit "ticket/zoom/#{article.ticket.id}"
  245. end
  246. within :active_ticket_article, article do
  247. click '.js-ArticleAction[data-type=delete]'
  248. end
  249. in_modal do
  250. click '.js-submit'
  251. end
  252. wait.until_disappears { find :active_ticket_article, article, wait: false }
  253. end
  254. end
  255. end
  256. context 'verifying permissions matrix' do
  257. shared_examples 'according to permission matrix' do |item:, expects_visible:, offset:, description:|
  258. context "looking at #{description} #{item}" do
  259. let(:item) { item }
  260. let(:offset) { offset }
  261. let(:matcher) { expects_visible ? :have_css : :have_no_css }
  262. it expects_visible ? 'delete button is visible' : 'delete button is not visible' do
  263. visit "ticket/zoom/#{article.ticket.id}"
  264. within :active_ticket_article, article do
  265. expect(page).to send(matcher, '.js-ArticleAction[data-type=delete]', wait: 0)
  266. end
  267. end
  268. end
  269. end
  270. shared_examples 'deleting ticket article' do |item:, now:, later:, much_later:|
  271. include_examples 'according to permission matrix', item: item, expects_visible: now, offset: 0.minutes, description: 'just created'
  272. include_examples 'according to permission matrix', item: item, expects_visible: later, offset: 6.minutes, description: 'few minutes old'
  273. include_examples 'according to permission matrix', item: item, expects_visible: much_later, offset: 11.minutes, description: 'very old'
  274. end
  275. context 'as admin' do
  276. let(:user) { admin }
  277. include_examples 'deleting ticket article',
  278. item: 'article_communication',
  279. now: false, later: false, much_later: false
  280. include_examples 'deleting ticket article',
  281. item: 'article_note_self',
  282. now: true, later: true, much_later: false
  283. include_examples 'deleting ticket article',
  284. item: 'article_note_other',
  285. now: false, later: false, much_later: false
  286. include_examples 'deleting ticket article',
  287. item: 'article_note_customer',
  288. now: false, later: false, much_later: false
  289. include_examples 'deleting ticket article',
  290. item: 'article_note_communication_self',
  291. now: true, later: true, much_later: false
  292. include_examples 'deleting ticket article',
  293. item: 'article_note_communication_other',
  294. now: false, later: false, much_later: false
  295. end
  296. context 'as agent' do
  297. let(:user) { agent }
  298. include_examples 'deleting ticket article',
  299. item: 'article_communication',
  300. now: false, later: false, much_later: false
  301. include_examples 'deleting ticket article',
  302. item: 'article_note_self',
  303. now: true, later: true, much_later: false
  304. include_examples 'deleting ticket article',
  305. item: 'article_note_other',
  306. now: false, later: false, much_later: false
  307. include_examples 'deleting ticket article',
  308. item: 'article_note_customer',
  309. now: false, later: false, much_later: false
  310. include_examples 'deleting ticket article',
  311. item: 'article_note_communication_self',
  312. now: true, later: true, much_later: false
  313. include_examples 'deleting ticket article',
  314. item: 'article_note_communication_other',
  315. now: false, later: false, much_later: false
  316. end
  317. context 'as customer' do
  318. let(:user) { customer }
  319. include_examples 'deleting ticket article',
  320. item: 'article_communication',
  321. now: false, later: false, much_later: false
  322. include_examples 'deleting ticket article',
  323. item: 'article_note_customer',
  324. now: false, later: false, much_later: false
  325. end
  326. context 'with custom offset' do
  327. let(:setting_delete_timeframe) { 6_000 }
  328. context 'as admin' do
  329. let(:user) { admin }
  330. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  331. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  332. end
  333. context 'as agent' do
  334. let(:user) { agent }
  335. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
  336. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
  337. end
  338. end
  339. context 'with timeframe as 0' do
  340. let(:setting_delete_timeframe) { 0 }
  341. context 'as agent' do
  342. let(:user) { agent }
  343. include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 99.days, description: 'long after'
  344. end
  345. end
  346. end
  347. context 'button is hidden on the go' do
  348. let(:setting_delete_timeframe) { 5 }
  349. let(:user) { agent }
  350. let(:item) { 'article_note_self' }
  351. let!(:article) { send(item) }
  352. let(:offset) { 0.seconds }
  353. it 'successfully' do
  354. visit "ticket/zoom/#{article.ticket.id}"
  355. within :active_ticket_article, article do
  356. find '.js-ArticleAction[data-type=delete]' # make sure delete button did show up
  357. expect(page).to have_no_css('.js-ArticleAction[data-type=delete]')
  358. end
  359. end
  360. end
  361. end
  362. context 'S/MIME active', authenticated_as: :authenticate do
  363. let(:system_email_address) { 'smime1@example.com' }
  364. let(:email_address) { create(:email_address, email: system_email_address) }
  365. let(:group) { create(:group, email_address: email_address) }
  366. let(:agent_groups) { [group] }
  367. let(:agent) { create(:agent, groups: agent_groups) }
  368. let(:sender_email_address) { 'smime2@example.com' }
  369. let(:customer) { create(:customer, email: sender_email_address) }
  370. let!(:ticket) { create(:ticket, group: group, owner: agent, customer: customer) }
  371. def authenticate
  372. Setting.set('smime_integration', true)
  373. agent
  374. end
  375. context 'received mail' do
  376. context 'article meta information' do
  377. context 'success' do
  378. it 'shows encryption/sign information' do
  379. create(:ticket_article, preferences: {
  380. security: {
  381. type: 'S/MIME',
  382. encryption: {
  383. success: true,
  384. comment: 'COMMENT_ENCRYPT_SUCCESS',
  385. },
  386. sign: {
  387. success: true,
  388. comment: 'COMMENT_SIGN_SUCCESS',
  389. },
  390. }
  391. }, ticket: ticket)
  392. visit "#ticket/zoom/#{ticket.id}"
  393. expect(page).to have_css('svg.icon-lock')
  394. expect(page).to have_css('svg.icon-signed')
  395. open_article_meta
  396. expect(page).to have_css('span', text: 'Encrypted')
  397. expect(page).to have_css('span', text: 'Signed')
  398. expect(page).to have_css('span[title=COMMENT_ENCRYPT_SUCCESS]')
  399. expect(page).to have_css('span[title=COMMENT_SIGN_SUCCESS]')
  400. end
  401. end
  402. context 'error' do
  403. it 'shows create information about encryption/sign failed' do
  404. create(:ticket_article, preferences: {
  405. security: {
  406. type: 'S/MIME',
  407. encryption: {
  408. success: false,
  409. comment: 'Encryption failed because XXX',
  410. },
  411. sign: {
  412. success: false,
  413. comment: 'Sign failed because XXX',
  414. },
  415. }
  416. }, ticket: ticket)
  417. visit "#ticket/zoom/#{ticket.id}"
  418. expect(page).to have_css('svg.icon-not-signed')
  419. open_article_meta
  420. expect(page).to have_css('div.alert.alert--warning', text: 'Encryption failed because XXX')
  421. expect(page).to have_css('div.alert.alert--warning', text: 'Sign failed because XXX')
  422. end
  423. end
  424. end
  425. context 'certificate not present at time of arrival' do
  426. it 'retry' do
  427. smime1 = create(:smime_certificate, :with_private, fixture: system_email_address)
  428. smime2 = create(:smime_certificate, :with_private, fixture: sender_email_address)
  429. mail = Channel::EmailBuild.build(
  430. from: sender_email_address,
  431. to: system_email_address,
  432. body: 'somebody with some text',
  433. content_type: 'text/plain',
  434. security: {
  435. type: 'S/MIME',
  436. sign: {
  437. success: true,
  438. },
  439. encryption: {
  440. success: true,
  441. },
  442. },
  443. )
  444. smime1.destroy
  445. smime2.destroy
  446. parsed_mail = Channel::EmailParser.new.parse(mail.to_s)
  447. ticket, article, _user, _mail = Channel::EmailParser.new.process({ group_id: group.id }, parsed_mail['raw'])
  448. expect(Ticket::Article.find(article.id).body).to eq('no visible content')
  449. create(:smime_certificate, fixture: sender_email_address)
  450. create(:smime_certificate, :with_private, fixture: system_email_address)
  451. visit "#ticket/zoom/#{ticket.id}"
  452. expect(page).to have_no_css('.article-content', text: 'somebody with some text')
  453. click '.js-securityRetryProcess'
  454. expect(page).to have_css('.article-content', text: 'somebody with some text')
  455. end
  456. end
  457. end
  458. context 'replying', authenticated_as: :setup_and_authenticate do
  459. def setup_and_authenticate
  460. create(:ticket_article, ticket: ticket, from: customer.email)
  461. create(:smime_certificate, :with_private, fixture: system_email_address)
  462. create(:smime_certificate, fixture: sender_email_address)
  463. authenticate
  464. end
  465. it 'plain' do
  466. visit "#ticket/zoom/#{ticket.id}"
  467. all('a[data-type=emailReply]').last.click
  468. find('.articleNewEdit-body').send_keys('Test')
  469. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  470. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  471. click '.js-securityEncrypt'
  472. click '.js-securitySign'
  473. click '.js-submit'
  474. expect(page).to have_css('.ticket-article-item', count: 2)
  475. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil
  476. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil
  477. end
  478. it 'signed' do
  479. visit "#ticket/zoom/#{ticket.id}"
  480. all('a[data-type=emailReply]').last.click
  481. find('.articleNewEdit-body').send_keys('Test')
  482. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  483. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  484. click '.js-securityEncrypt'
  485. click '.js-submit'
  486. expect(page).to have_css('.ticket-article-item', count: 2)
  487. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be nil
  488. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true
  489. end
  490. it 'encrypted' do
  491. visit "#ticket/zoom/#{ticket.id}"
  492. all('a[data-type=emailReply]').last.click
  493. find('.articleNewEdit-body').send_keys('Test')
  494. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  495. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  496. click '.js-securitySign'
  497. click '.js-submit'
  498. expect(page).to have_css('.ticket-article-item', count: 2)
  499. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true
  500. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be nil
  501. end
  502. it 'signed and encrypted' do
  503. visit "#ticket/zoom/#{ticket.id}"
  504. all('a[data-type=emailReply]').last.click
  505. find('.articleNewEdit-body').send_keys('Test')
  506. expect(page).to have_css('.js-securityEncrypt.btn--active', wait: 5)
  507. expect(page).to have_css('.js-securitySign.btn--active', wait: 5)
  508. click '.js-submit'
  509. expect(page).to have_css('.ticket-article-item', count: 2)
  510. expect(Ticket::Article.last.preferences['security']['encryption']['success']).to be true
  511. expect(Ticket::Article.last.preferences['security']['sign']['success']).to be true
  512. end
  513. end
  514. context 'Group default behavior' do
  515. let(:smime_config) { {} }
  516. def authenticate
  517. Setting.set('smime_integration', true)
  518. Setting.set('smime_config', smime_config)
  519. create(:ticket_article, ticket: ticket, from: customer.email)
  520. create(:smime_certificate, :with_private, fixture: system_email_address)
  521. create(:smime_certificate, fixture: sender_email_address)
  522. agent
  523. end
  524. shared_examples 'security defaults example' do |sign:, encrypt:|
  525. it "security defaults sign: #{sign}, encrypt: #{encrypt}" do
  526. within(:active_content) do
  527. encrypt_button = find('.js-securityEncrypt', wait: 5)
  528. sign_button = find('.js-securitySign', wait: 5)
  529. await_empty_ajax_queue
  530. active_button_class = '.btn--active'
  531. expect(encrypt_button.matches_css?(active_button_class, wait: 2)).to be(encrypt)
  532. expect(sign_button.matches_css?(active_button_class, wait: 2)).to be(sign)
  533. end
  534. end
  535. end
  536. shared_examples 'security defaults' do |sign:, encrypt:|
  537. before do
  538. visit "#ticket/zoom/#{ticket.id}"
  539. within(:active_content) do
  540. all('a[data-type=emailReply]').last.click
  541. find('.articleNewEdit-body').send_keys('Test')
  542. await_empty_ajax_queue
  543. end
  544. end
  545. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  546. end
  547. shared_examples 'security defaults group change' do |sign:, encrypt:|
  548. before do
  549. visit "#ticket/zoom/#{ticket.id}"
  550. within(:active_content) do
  551. all('a[data-type=emailReply]').last.click
  552. find('.articleNewEdit-body').send_keys('Test')
  553. await_empty_ajax_queue
  554. select new_group.name, from: 'group_id'
  555. end
  556. end
  557. include_examples 'security defaults example', sign: sign, encrypt: encrypt
  558. end
  559. context 'not configured' do
  560. it_behaves_like 'security defaults', sign: true, encrypt: true
  561. end
  562. context 'configuration present' do
  563. let(:smime_config) do
  564. {
  565. 'group_id' => group_defaults
  566. }
  567. end
  568. let(:group_defaults) do
  569. {
  570. 'default_encryption' => {
  571. group.id.to_s => default_encryption,
  572. },
  573. 'default_sign' => {
  574. group.id.to_s => default_sign,
  575. }
  576. }
  577. end
  578. let(:default_sign) { true }
  579. let(:default_encryption) { true }
  580. shared_examples 'sign and encrypt variations' do |check_examples_name|
  581. it_behaves_like check_examples_name, sign: true, encrypt: true
  582. context 'no value' do
  583. let(:group_defaults) { {} }
  584. it_behaves_like check_examples_name, sign: true, encrypt: true
  585. end
  586. context 'signing disabled' do
  587. let(:default_sign) { false }
  588. it_behaves_like check_examples_name, sign: false, encrypt: true
  589. end
  590. context 'encryption disabled' do
  591. let(:default_encryption) { false }
  592. it_behaves_like check_examples_name, sign: true, encrypt: false
  593. end
  594. end
  595. context 'same Group' do
  596. it_behaves_like 'sign and encrypt variations', 'security defaults'
  597. end
  598. context 'Group change' do
  599. let(:new_group) { create(:group, email_address: email_address) }
  600. let(:agent_groups) { [group, new_group] }
  601. let(:group_defaults) do
  602. {
  603. 'default_encryption' => {
  604. new_group.id.to_s => default_encryption,
  605. },
  606. 'default_sign' => {
  607. new_group.id.to_s => default_sign,
  608. }
  609. }
  610. end
  611. it_behaves_like 'sign and encrypt variations', 'security defaults group change'
  612. end
  613. end
  614. end
  615. end
  616. describe 'linking Knowledge Base answer' do
  617. include_context 'basic Knowledge Base'
  618. let(:ticket) { create :ticket, group: Group.find_by(name: 'Users') }
  619. let(:answer) { published_answer }
  620. let(:translation) { answer.translations.first }
  621. shared_examples 'verify linking' do
  622. it 'allows to look up an answer' do
  623. visit "#ticket/zoom/#{ticket.id}"
  624. within :active_content do
  625. within '.link_kb_answers' do
  626. find('.js-add').click
  627. find('.js-input').send_keys translation.title
  628. find(%(li[data-value="#{translation.id}"])).click
  629. expect(find('.link_kb_answers ol')).to have_text translation.title
  630. end
  631. end
  632. end
  633. end
  634. context 'with ES', searchindex: true, authenticated_as: :authenticate do
  635. def authenticate
  636. configure_elasticsearch(required: true, rebuild: true) do
  637. answer
  638. end
  639. true
  640. end
  641. include_examples 'verify linking'
  642. end
  643. context 'without ES', authenticated_as: :authenticate do
  644. def authenticate
  645. answer
  646. true
  647. end
  648. include_examples 'verify linking'
  649. end
  650. end
  651. describe 'forwarding article with an image' do
  652. let(:ticket_article_body) do
  653. filename = 'squares.png'
  654. file = File.binread(Rails.root.join("spec/fixtures/image/#{filename}"))
  655. ext = File.extname(filename)[1...]
  656. base64 = Base64.encode64(file).delete("\n")
  657. "<img style='width: 1004px; max-width: 100%;' src=\\\"data:image/#{ext};base64,#{base64}\\\"><br>"
  658. end
  659. def current_ticket
  660. Ticket.find current_url.split('/').last
  661. end
  662. def create_ticket
  663. visit '#ticket/create'
  664. within :active_content do
  665. find('[data-type=email-out]').click
  666. find('[name=title]').fill_in with: 'Title'
  667. find('[name=customer_id_completion]').fill_in with: 'customer@example.com'
  668. find('[name=group_id]').select 'Users'
  669. find(:richtext).execute_script "this.innerHTML = \"#{ticket_article_body}\""
  670. find('.js-submit').click
  671. end
  672. await_empty_ajax_queue
  673. end
  674. def forward
  675. within :active_content do
  676. click '.js-ArticleAction[data-type=emailForward]'
  677. fill_in 'To', with: 'customer@example.com'
  678. find('.js-submit').click
  679. end
  680. await_empty_ajax_queue
  681. end
  682. def images_identical?(image_a, image_b)
  683. return false if image_a.height != image_b.height
  684. return false if image_a.width != image_b.width
  685. image_a.height.times do |y|
  686. image_a.row(y).each_with_index do |pixel, x|
  687. return false if pixel != image_b[x, y]
  688. end
  689. end
  690. true
  691. end
  692. it 'keeps image intact' do
  693. create_ticket
  694. forward
  695. images = current_ticket.articles.map do |article|
  696. ChunkyPNG::Image.from_string article.attachments.first.content
  697. end
  698. expect(images_identical?(images.first, images.second)).to be(true)
  699. end
  700. end
  701. # https://github.com/zammad/zammad/issues/3335
  702. context 'ticket state sort order maintained when locale is de-de', authenticated_as: :authenticate do
  703. def authenticate
  704. user.preferences[:locale] = 'de-de'
  705. user
  706. end
  707. context 'when existing ticket is open' do
  708. let(:user) { create(:customer) }
  709. let(:ticket) { create(:ticket, customer: user) }
  710. it 'shows ticket state dropdown options in sorted order, with new at the end' do
  711. visit "ticket/zoom/#{ticket.id}"
  712. await_empty_ajax_queue
  713. expect(all('select[name=state_id] option').map(&:text)).to eq(%w[geschlossen offen neu])
  714. end
  715. end
  716. context 'when a new ticket is created' do
  717. let(:user) { create(:agent, groups: [permitted_group]) }
  718. let(:permitted_group) { create(:group) }
  719. it 'shows ticket state dropdown options in sorted order, with new in sorted position' do
  720. visit 'ticket/create'
  721. await_empty_ajax_queue
  722. expect(all('select[name=state_id] option').map(&:text)).to eq ['-', 'geschlossen', 'neu', 'offen', 'warten auf Erinnerung', 'warten auf schliessen']
  723. end
  724. end
  725. end
  726. context 'object manager attribute permission view' do
  727. let!(:group_users) { Group.find_by(name: 'Users') }
  728. shared_examples 'shows attributes and values for agent view and editable' do
  729. it 'shows attributes and values for agent view and editable', authenticated_as: :current_user do
  730. visit "ticket/zoom/#{ticket.id}"
  731. refresh # refresh to have assets generated for ticket
  732. expect(page).to have_select('state_id', options: ['new', 'open', 'pending reminder', 'pending close', 'closed'])
  733. expect(page).to have_select('priority_id')
  734. expect(page).to have_select('owner_id')
  735. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  736. end
  737. end
  738. shared_examples 'shows attributes and values for agent view but disabled' do
  739. it 'shows attributes and values for agent view but disabled', authenticated_as: :current_user do
  740. visit "ticket/zoom/#{ticket.id}"
  741. refresh # refresh to have assets generated for ticket
  742. expect(page).to have_css('select[name=state_id][disabled]')
  743. expect(page).to have_css('select[name=priority_id][disabled]')
  744. expect(page).to have_css('select[name=owner_id][disabled]')
  745. expect(page).to have_css('div.tabsSidebar-tab[data-tab=customer]')
  746. end
  747. end
  748. shared_examples 'shows attributes and values for customer view' do
  749. it 'shows attributes and values for customer view', authenticated_as: :current_user do
  750. visit "ticket/zoom/#{ticket.id}"
  751. refresh # refresh to have assets generated for ticket
  752. expect(page).to have_select('state_id', options: %w[new open closed])
  753. expect(page).to have_no_select('priority_id')
  754. expect(page).to have_no_select('owner_id')
  755. expect(page).to have_no_css('div.tabsSidebar-tab[data-tab=customer]')
  756. end
  757. end
  758. context 'as customer' do
  759. let!(:current_user) { create(:customer) }
  760. let(:ticket) { create(:ticket, customer: current_user) }
  761. include_examples 'shows attributes and values for customer view'
  762. end
  763. context 'as agent with full permissions' do
  764. let(:current_user) { create(:agent, groups: [ group_users ] ) }
  765. let(:ticket) { create(:ticket, group: group_users ) }
  766. include_examples 'shows attributes and values for agent view and editable'
  767. end
  768. context 'as agent with change permissions' do
  769. let!(:current_user) { create(:agent) }
  770. let(:ticket) { create(:ticket, group: group_users) }
  771. before do
  772. current_user.group_names_access_map = {
  773. group_users.name => %w[read change],
  774. }
  775. end
  776. include_examples 'shows attributes and values for agent view and editable'
  777. end
  778. context 'as agent with read permissions' do
  779. let!(:current_user) { create(:agent) }
  780. let(:ticket) { create(:ticket, group: group_users) }
  781. before do
  782. current_user.group_names_access_map = {
  783. group_users.name => 'read',
  784. }
  785. end
  786. include_examples 'shows attributes and values for agent view but disabled'
  787. end
  788. context 'as agent+customer with full permissions' do
  789. let!(:current_user) { create(:agent_and_customer, groups: [ group_users ] ) }
  790. context 'normal ticket' do
  791. let(:ticket) { create(:ticket, group: group_users ) }
  792. include_examples 'shows attributes and values for agent view and editable'
  793. end
  794. context 'ticket where current_user is also customer' do
  795. let(:ticket) { create(:ticket, customer: current_user, group: group_users ) }
  796. include_examples 'shows attributes and values for agent view and editable'
  797. end
  798. end
  799. context 'as agent+customer with change permissions' do
  800. let!(:current_user) { create(:agent_and_customer) }
  801. before do
  802. current_user.group_names_access_map = {
  803. group_users.name => %w[read change],
  804. }
  805. end
  806. context 'normal ticket' do
  807. let(:ticket) { create(:ticket, group: group_users) }
  808. include_examples 'shows attributes and values for agent view and editable'
  809. end
  810. context 'ticket where current_user is also customer' do
  811. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  812. include_examples 'shows attributes and values for agent view and editable'
  813. end
  814. end
  815. context 'as agent+customer with read permissions' do
  816. let!(:current_user) { create(:agent_and_customer) }
  817. before do
  818. current_user.group_names_access_map = {
  819. group_users.name => 'read',
  820. }
  821. end
  822. context 'normal ticket' do
  823. let(:ticket) { create(:ticket, group: group_users) }
  824. include_examples 'shows attributes and values for agent view but disabled'
  825. end
  826. context 'ticket where current_user is also customer' do
  827. let(:ticket) { create(:ticket, customer: current_user, group: group_users) }
  828. include_examples 'shows attributes and values for agent view but disabled'
  829. end
  830. end
  831. context 'as agent+customer but only customer for the ticket (no agent access)' do
  832. let!(:current_user) { create(:agent_and_customer) }
  833. let(:ticket) { create(:ticket, customer: current_user) }
  834. include_examples 'shows attributes and values for customer view'
  835. end
  836. end
  837. describe 'note visibility', authenticated_as: :customer do
  838. context 'when logged in as a customer' do
  839. let(:customer) { create(:customer) }
  840. let(:ticket) { create(:ticket, customer: customer) }
  841. let!(:ticket_article) { create(:ticket_article, ticket: ticket) }
  842. let!(:ticket_note) { create(:ticket_article, ticket: ticket, internal: true, type_name: 'note') }
  843. it 'previously created private note is not visible' do
  844. visit "ticket/zoom/#{ticket_article.ticket.id}"
  845. expect(page).to have_no_selector(:active_ticket_article, ticket_note)
  846. end
  847. it 'previously created private note shows up via WS push' do
  848. visit "ticket/zoom/#{ticket_article.ticket.id}"
  849. # make sure ticket is done loading and change will be pushed via WS
  850. find(:active_ticket_article, ticket_article)
  851. await_empty_ajax_queue
  852. ticket_note.update!(internal: false)
  853. expect(page).to have_selector(:active_ticket_article, ticket_note)
  854. end
  855. end
  856. end
  857. # https://github.com/zammad/zammad/issues/3012
  858. describe 'article type selection' do
  859. context 'when logged in as a customer', authenticated_as: :customer do
  860. let(:customer) { create(:customer) }
  861. let(:ticket) { create(:ticket, customer: customer) }
  862. it 'hides button for single choice' do
  863. visit "ticket/zoom/#{ticket.id}"
  864. find('.articleNewEdit-body').send_keys('Some reply')
  865. expect(page).to have_no_selector('.js-selectedArticleType')
  866. end
  867. end
  868. context 'when logged in as an agent' do
  869. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  870. it 'shows button for multiple choices' do
  871. visit "ticket/zoom/#{ticket.id}"
  872. find('.articleNewEdit-body').send_keys('Some reply')
  873. expect(page).to have_selector('.js-selectedArticleType')
  874. end
  875. end
  876. end
  877. # https://github.com/zammad/zammad/issues/3260
  878. describe 'next in overview macro changes URL', authenticated_as: :authenticate do
  879. let(:next_ticket) { create(:ticket, title: 'next Ticket', group: Group.first) }
  880. let(:macro) { create(:macro, name: 'next macro', ux_flow_next_up: 'next_from_overview') }
  881. def authenticate
  882. next_ticket && macro
  883. true
  884. end
  885. it 'to next Ticket ID' do
  886. visit 'ticket/view/all_unassigned'
  887. click_on 'Welcome to Zammad!'
  888. click '.js-openDropdownMacro'
  889. find(:macro, macro.id).click
  890. wait(5, interval: 1).until_constant { current_url }
  891. expect(current_url).to include("ticket/zoom/#{next_ticket.id}")
  892. end
  893. end
  894. # https://github.com/zammad/zammad/issues/3279
  895. describe 'previous/next clickability when at last or first ticket' do
  896. let(:ticket_a) { create(:ticket, title: 'ticket a', group: Group.first) }
  897. let(:ticket_b) { create(:ticket, title: 'ticket b', group: Group.first) }
  898. before do
  899. ticket_a && ticket_b
  900. visit 'ticket/view/all_unassigned'
  901. end
  902. it 'previous is not clickable for the first item' do
  903. open_nth_item(0)
  904. expect { click '.pagination .previous' }.not_to change { current_url }
  905. end
  906. it 'next is clickable for the first item' do
  907. open_nth_item(0)
  908. expect { click '.pagination .next' }.to change { current_url }
  909. end
  910. it 'previous is clickable for the middle item' do
  911. open_nth_item(1)
  912. expect { click '.pagination .previous' }.to change { current_url }
  913. end
  914. it 'next is clickable for the middle item' do
  915. open_nth_item(1)
  916. expect { click '.pagination .next' }.to change { current_url }
  917. end
  918. it 'previous is clickable for the last item' do
  919. open_nth_item(2)
  920. expect { click '.pagination .previous' }.to change { current_url }
  921. end
  922. it 'next is not clickable for the last item' do
  923. open_nth_item(2)
  924. expect { click '.pagination .next' }.not_to change { current_url }
  925. end
  926. def open_nth_item(nth)
  927. within :active_content do
  928. find_all('.table tr.item a[href^="#ticket/zoom"]')[nth].click
  929. end
  930. await_empty_ajax_queue
  931. end
  932. end
  933. # https://github.com/zammad/zammad/issues/3267
  934. describe 'previous/next buttons are added when open ticket is opened from overview' 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. # prepare an opened ticket and go to overview
  938. before do
  939. ticket_a && ticket_b
  940. visit "ticket/zoom/#{ticket_a.id}"
  941. await_empty_ajax_queue
  942. visit 'ticket/view/all_unassigned'
  943. end
  944. it 'adds previous/next buttons to existing ticket' do
  945. within :active_content do
  946. click_on ticket_a.title
  947. expect(page).to have_css('.pagination-counter')
  948. end
  949. end
  950. it 'keeps previous/next buttons when navigating to overview ticket from elsewhere' do
  951. within :active_content do
  952. click_on ticket_a.title
  953. visit 'dashboard'
  954. visit "ticket/zoom/#{ticket_a.id}"
  955. expect(page).to have_css('.pagination-counter')
  956. end
  957. end
  958. end
  959. # https://github.com/zammad/zammad/issues/2942
  960. describe 'attachments are lost in specific conditions' do
  961. let(:ticket) { create(:ticket, group: Group.first) }
  962. it 'attachment is retained when forwarding a fresh article' do
  963. ensure_websocket do
  964. visit "ticket/zoom/#{ticket.id}"
  965. end
  966. # add an article, forcing reset of form_id
  967. # click in the upper most upper left corner of the article create textbox
  968. # (that works for both Firefox and Chrome)
  969. # to avoid clicking on attachment upload
  970. find('.js-writeArea').click({ x: 5, y: 5 })
  971. # wait for propagateOpenTextarea to be completed
  972. find('.attachmentPlaceholder-label').in_fixed_position
  973. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  974. # write article content
  975. find('.articleNewEdit-body').send_keys('Some reply')
  976. click '.js-submit'
  977. # wait for article to be added to the page
  978. expect(page).to have_css('.ticket-article-item', count: 1)
  979. await_empty_ajax_queue
  980. # create a on-the-fly article with attachment that will get pushed to open browser
  981. article1 = create(:ticket_article, ticket: ticket)
  982. Store.add(
  983. object: 'Ticket::Article',
  984. o_id: article1.id,
  985. data: 'some content',
  986. filename: 'some_file.txt',
  987. preferences: {
  988. 'Content-Type' => 'text/plain',
  989. },
  990. created_by_id: 1,
  991. )
  992. # wait for article to be added to the page
  993. expect(page).to have_css('.ticket-article-item', count: 2, wait: 10)
  994. await_empty_ajax_queue
  995. # click on forward of created article
  996. within :active_ticket_article, article1 do
  997. find('a[data-type=emailForward]').click
  998. end
  999. # wait for propagateOpenTextarea to be completed
  1000. find('.attachmentPlaceholder-label').in_fixed_position
  1001. expect(page).to have_no_css('.attachmentPlaceholder-hint')
  1002. # fill forward information and create article
  1003. fill_in 'To', with: 'forward@example.org'
  1004. find('.articleNewEdit-body').send_keys('Forwarding with the attachment')
  1005. click '.js-submit'
  1006. # wait for article to be added to the page
  1007. await_empty_ajax_queue
  1008. expect(page).to have_css('.ticket-article-item', count: 3)
  1009. # check if attachment was forwarded successfully
  1010. within :active_ticket_article, ticket.reload.articles.last do
  1011. within '.attachments--list' do
  1012. expect(page).to have_text('some_file.txt')
  1013. end
  1014. end
  1015. end
  1016. end
  1017. describe 'mentions' do
  1018. context 'when logged in as agent' do
  1019. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1020. let!(:other_agent) { create(:agent, groups: [Group.find_by(name: 'Users')]) }
  1021. it 'can subscribe and unsubscribe' do
  1022. ensure_websocket do
  1023. visit "ticket/zoom/#{ticket.id}"
  1024. click '.js-subscriptions .js-subscribe input'
  1025. expect(page).to have_selector('.js-subscriptions .js-unsubscribe input', wait: 10)
  1026. expect(page).to have_selector('.js-subscriptions span.avatar', wait: 10)
  1027. click '.js-subscriptions .js-unsubscribe input'
  1028. expect(page).to have_selector('.js-subscriptions .js-subscribe input', wait: 10)
  1029. expect(page).to have_no_selector('.js-subscriptions span.avatar', wait: 10)
  1030. create(:mention, mentionable: ticket, user: other_agent)
  1031. expect(page).to have_selector('.js-subscriptions span.avatar', wait: 10)
  1032. # check history for mention entries
  1033. click 'h2.sidebar-header-headline.js-headline'
  1034. click 'li[data-type=ticket-history] a'
  1035. expect(page).to have_text('created Mention', wait: 10)
  1036. expect(page).to have_text('removed Mention', wait: 10)
  1037. end
  1038. end
  1039. end
  1040. end
  1041. # https://github.com/zammad/zammad/issues/2671
  1042. describe 'Pending time field in ticket sidebar', authenticated_as: :customer do
  1043. let(:customer) { create(:customer) }
  1044. let(:ticket) { create(:ticket, customer: customer, pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder')) }
  1045. it 'not shown to customer' do
  1046. visit "ticket/zoom/#{ticket.id}"
  1047. await_empty_ajax_queue
  1048. within :active_content do
  1049. expect(page).to have_no_css('.controls[data-name=pending_time]')
  1050. end
  1051. end
  1052. end
  1053. describe 'Pending time field in ticket sidebar as agent' do
  1054. before do
  1055. ticket.update(pending_time: 1.day.from_now, state: Ticket::State.lookup(name: 'pending reminder'))
  1056. visit "ticket/zoom/#{ticket.id}"
  1057. await_empty_ajax_queue
  1058. end
  1059. let(:ticket) { Ticket.first }
  1060. let(:elem) { find('.js-timepicker') }
  1061. # has to run asynchronously to keep both Firefox and Safari
  1062. # https://github.com/zammad/zammad/issues/3414
  1063. # https://github.com/zammad/zammad/issues/2887
  1064. context 'when clicking timepicker component' do
  1065. it 'in the first half, hours selected' do
  1066. within :active_content do
  1067. elem.click({ x: 10, y: 20 })
  1068. expect(elem).to have_selection(0..2)
  1069. end
  1070. end
  1071. it 'in the second half, minutes selected' do
  1072. within :active_content do
  1073. elem.click({ x: 35, y: 20 })
  1074. expect(elem).to have_selection(3..5)
  1075. end
  1076. end
  1077. end
  1078. matcher :have_selection do
  1079. match { starts_at == expected.begin && ends_at == expected.end }
  1080. def starts_at
  1081. actual.evaluate_script 'this.selectionStart'
  1082. end
  1083. def ends_at
  1084. actual.evaluate_script 'this.selectionEnd'
  1085. end
  1086. end
  1087. end
  1088. describe 'Article ID URL / link' do
  1089. let(:ticket) { create(:ticket, group: Group.first) }
  1090. let!(:article) { create(:'ticket/article', ticket: ticket) }
  1091. let(:url) { "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/#ticket/zoom/#{ticket.id}/#{article.id}" }
  1092. it 'shows Article direct link' do
  1093. ensure_websocket do
  1094. visit "ticket/zoom/#{ticket.id}"
  1095. await_empty_ajax_queue
  1096. within :active_ticket_article, article do
  1097. expect(page).to have_css(%(a[href="#{url}"]))
  1098. end
  1099. end
  1100. end
  1101. context 'when multiple Articles are present' do
  1102. let(:article_count) { 20 }
  1103. let(:article_at_the_top) { ticket.articles.first }
  1104. let(:article_in_the_middle) { ticket.articles[ article_count / 2 ] }
  1105. let(:article_at_the_bottom) { ticket.articles.last }
  1106. before do
  1107. article_count.times do
  1108. create(:'ticket/article', ticket: ticket, body: SecureRandom.uuid)
  1109. end
  1110. end
  1111. it 'scrolls to given Article ID' do
  1112. ensure_websocket do
  1113. visit "ticket/zoom/#{ticket.id}/#{article_in_the_middle.id}"
  1114. await_empty_ajax_queue
  1115. # workaround because browser scrolls in test initially to the bottom
  1116. # maybe because the articles are not present?!
  1117. refresh
  1118. # scroll to article in the middle of the page
  1119. within :active_content do
  1120. find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5)
  1121. expect(find("div#article-content-#{article_at_the_top.id}")).to be_obscured
  1122. expect(find("div#article-content-#{article_in_the_middle.id}")).not_to be_obscured
  1123. expect(find("div#article-content-#{article_at_the_bottom.id}")).to be_obscured
  1124. end
  1125. # scroll to article at the top of the page
  1126. visit "ticket/zoom/#{ticket.id}/#{article_at_the_top.id}"
  1127. await_empty_ajax_queue
  1128. within :active_content do
  1129. find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5)
  1130. expect(find("div#article-content-#{article_at_the_top.id}")).not_to be_obscured
  1131. expect(find("div#article-content-#{article_in_the_middle.id}")).to be_obscured
  1132. expect(find("div#article-content-#{article_at_the_bottom.id}")).to be_obscured
  1133. end
  1134. # scroll to article at the bottom of the page
  1135. visit "ticket/zoom/#{ticket.id}/#{article_at_the_bottom.id}"
  1136. await_empty_ajax_queue
  1137. within :active_content do
  1138. find("div#article-content-#{article_in_the_middle.id}").in_fixed_position(wait: 0.5)
  1139. expect(find("div#article-content-#{article_at_the_top.id}")).to be_obscured
  1140. expect(find("div#article-content-#{article_in_the_middle.id}")).to be_obscured
  1141. expect(find("div#article-content-#{article_at_the_bottom.id}")).not_to be_obscured
  1142. end
  1143. end
  1144. end
  1145. end
  1146. context 'when long articles are present' do
  1147. it 'will properly show the "See more" link if you switch between the ticket and the dashboard on new articles' do
  1148. ensure_websocket do
  1149. visit "ticket/zoom/#{ticket.id}"
  1150. await_empty_ajax_queue
  1151. visit 'dashboard'
  1152. expect(page).to have_css("a.js-dashboardMenuItem[data-key='Dashboard'].is-active", wait: 10)
  1153. article_id = create(:'ticket/article', ticket: ticket, body: "#{SecureRandom.uuid} #{"lorem ipsum\n" * 200}")
  1154. expect(page).to have_css('div.tasks a.is-modified', wait: 10)
  1155. visit "ticket/zoom/#{ticket.id}"
  1156. within :active_content do
  1157. expect(find("div#article-content-#{article_id.id}")).to have_text('See more')
  1158. end
  1159. end
  1160. end
  1161. end
  1162. end
  1163. describe 'Macros', authenticated_as: :authenticate do
  1164. let(:macro_body) { 'macro <b>body</b>' }
  1165. let(:macro) { create :macro, perform: { 'article.note' => { 'body' => macro_body, 'internal' => 'true', 'subject' => 'macro note' } } }
  1166. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1167. def authenticate
  1168. macro
  1169. true
  1170. end
  1171. it 'does html macro by default' do
  1172. visit "ticket/zoom/#{ticket.id}"
  1173. find('.js-openDropdownMacro').click
  1174. find(:macro, macro.id).click
  1175. await_empty_ajax_queue
  1176. expect(ticket.reload.articles.last.body).to eq(macro_body)
  1177. expect(ticket.reload.articles.last.content_type).to eq('text/html')
  1178. end
  1179. end
  1180. describe 'object manager attributes maxlength', authenticated_as: :authenticate, db_strategy: :reset do
  1181. let(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1182. def authenticate
  1183. ticket
  1184. create :object_manager_attribute_text, name: 'maxtest', display: 'maxtest', screens: attributes_for(:required_screen), data_option: {
  1185. 'type' => 'text',
  1186. 'maxlength' => 3,
  1187. 'null' => true,
  1188. 'translate' => false,
  1189. 'default' => '',
  1190. 'options' => {},
  1191. 'relation' => '',
  1192. }
  1193. ObjectManager::Attribute.migration_execute
  1194. true
  1195. end
  1196. it 'checks ticket zoom' do
  1197. visit "ticket/zoom/#{ticket.id}"
  1198. within(:active_content) do
  1199. fill_in 'maxtest', with: 'hellu'
  1200. expect(page.find_field('maxtest').value).to eq('hel')
  1201. end
  1202. end
  1203. end
  1204. describe 'GitLab Integration', :integration, authenticated_as: :authenticate do
  1205. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1206. before(:all) do # rubocop:disable RSpec/BeforeAfterAll
  1207. required_envs = %w[GITLAB_ENDPOINT GITLAB_APITOKEN]
  1208. required_envs.each do |key|
  1209. skip("NOTICE: Missing environment variable #{key} for test! (Please fill up: #{required_envs.join(' && ')})") if ENV[key].blank?
  1210. end
  1211. end
  1212. def authenticate
  1213. Setting.set('gitlab_integration', true)
  1214. Setting.set('gitlab_config', {
  1215. api_token: ENV['GITLAB_APITOKEN'],
  1216. endpoint: ENV['GITLAB_ENDPOINT'],
  1217. })
  1218. true
  1219. end
  1220. it 'creates links and removes them' do
  1221. visit "#ticket/zoom/#{ticket.id}"
  1222. within(:active_content) do
  1223. # switch to GitLab sidebar
  1224. click('.tabsSidebar-tab[data-tab=gitlab]')
  1225. click('.sidebar-header-headline.js-headline')
  1226. # add issue
  1227. click_on 'Link issue'
  1228. fill_in 'link', with: ENV['GITLAB_ISSUE_LINK']
  1229. click_on 'Submit'
  1230. await_empty_ajax_queue
  1231. # verify issue
  1232. content = find('.sidebar-git-issue-content')
  1233. expect(content).to have_text('#1 Example issue')
  1234. expect(content).to have_text('critical')
  1235. expect(content).to have_text('special')
  1236. expect(content).to have_text('important milestone')
  1237. expect(content).to have_text('zammad-robot')
  1238. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to eq(ENV['GITLAB_ISSUE_LINK'])
  1239. # check sidebar counter increased to 1
  1240. expect(find('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')).to have_text('1')
  1241. # delete issue
  1242. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITLAB_ISSUE_LINK']}']")
  1243. await_empty_ajax_queue
  1244. content = find('.sidebar[data-tab=gitlab] .sidebar-content')
  1245. expect(content).to have_text('No linked issues')
  1246. expect(ticket.reload.preferences[:gitlab][:issue_links][0]).to be nil
  1247. # check that counter got removed
  1248. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=gitlab] .js-tabCounter')
  1249. end
  1250. end
  1251. end
  1252. describe 'GitHub Integration', :integration, authenticated_as: :authenticate do
  1253. let!(:ticket) { create(:ticket, group: Group.find_by(name: 'Users')) }
  1254. before(:all) do # rubocop:disable RSpec/BeforeAfterAll
  1255. required_envs = %w[GITHUB_ENDPOINT GITHUB_APITOKEN]
  1256. required_envs.each do |key|
  1257. skip("NOTICE: Missing environment variable #{key} for test! (Please fill up: #{required_envs.join(' && ')})") if ENV[key].blank?
  1258. end
  1259. end
  1260. def authenticate
  1261. Setting.set('github_integration', true)
  1262. Setting.set('github_config', {
  1263. api_token: ENV['GITHUB_APITOKEN'],
  1264. endpoint: ENV['GITHUB_ENDPOINT'],
  1265. })
  1266. true
  1267. end
  1268. it 'creates links and removes them' do
  1269. visit "#ticket/zoom/#{ticket.id}"
  1270. within(:active_content) do
  1271. # switch to GitHub sidebar
  1272. click('.tabsSidebar-tab[data-tab=github]')
  1273. click('.sidebar-header-headline.js-headline')
  1274. # add issue
  1275. click_on 'Link issue'
  1276. fill_in 'link', with: ENV['GITHUB_ISSUE_LINK']
  1277. click_on 'Submit'
  1278. await_empty_ajax_queue
  1279. # verify issue
  1280. content = find('.sidebar-git-issue-content')
  1281. expect(content).to have_text('#1575 GitHub integration')
  1282. expect(content).to have_text('feature backlog')
  1283. expect(content).to have_text('integration')
  1284. expect(content).to have_text('4.0')
  1285. expect(content).to have_text('Thorsten')
  1286. expect(ticket.reload.preferences[:github][:issue_links][0]).to eq(ENV['GITHUB_ISSUE_LINK'])
  1287. # check sidebar counter increased to 1
  1288. expect(find('.tabsSidebar-tab[data-tab=github] .js-tabCounter')).to have_text('1')
  1289. # delete issue
  1290. click(".sidebar-git-issue-delete span[data-issue-id='#{ENV['GITHUB_ISSUE_LINK']}']")
  1291. await_empty_ajax_queue
  1292. content = find('.sidebar[data-tab=github] .sidebar-content')
  1293. expect(content).to have_text('No linked issues')
  1294. expect(ticket.reload.preferences[:github][:issue_links][0]).to be nil
  1295. # check that counter got removed
  1296. expect(page).to have_no_selector('.tabsSidebar-tab[data-tab=github] .js-tabCounter')
  1297. end
  1298. end
  1299. end
  1300. context 'Sidebar - Open & Closed Tickets', searchindex: true, authenticated_as: :authenticate do
  1301. let(:customer) { create(:customer, :with_org) }
  1302. let(:ticket_open) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, title: SecureRandom.uuid) }
  1303. let(:ticket_closed) { create(:ticket, group: Group.find_by(name: 'Users'), customer: customer, state: Ticket::State.find_by(name: 'closed'), title: SecureRandom.uuid) }
  1304. def authenticate
  1305. ticket_open
  1306. ticket_closed
  1307. configure_elasticsearch(required: true, rebuild: true)
  1308. Scheduler.worker(true)
  1309. true
  1310. end
  1311. it 'does show open and closed tickets in advanced search url' do
  1312. visit "#ticket/zoom/#{ticket_open.id}"
  1313. click '.tabsSidebar-tab[data-tab=customer]'
  1314. click '.user-tickets[data-type=open]'
  1315. expect(page).to have_text(ticket_open.title, wait: 20)
  1316. visit "#ticket/zoom/#{ticket_open.id}"
  1317. click '.tabsSidebar-tab[data-tab=customer]'
  1318. click '.user-tickets[data-type=closed]'
  1319. expect(page).to have_text(ticket_closed.title, wait: 20)
  1320. end
  1321. end
  1322. end