ticket_spec.rb 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. require 'rails_helper'
  2. require 'models/application_model_examples'
  3. require 'models/concerns/can_be_imported_examples'
  4. require 'models/concerns/can_lookup_examples'
  5. require 'models/concerns/has_xss_sanitized_note_examples'
  6. require 'models/concerns/has_object_manager_attributes_validation_examples'
  7. RSpec.describe Ticket, type: :model do
  8. it_behaves_like 'ApplicationModel'
  9. it_behaves_like 'CanBeImported'
  10. it_behaves_like 'CanLookup'
  11. it_behaves_like 'HasXssSanitizedNote', model_factory: :ticket
  12. it_behaves_like 'HasObjectManagerAttributesValidation'
  13. subject(:ticket) { create(:ticket) }
  14. describe 'Class methods:' do
  15. describe '.selectors' do
  16. # https://github.com/zammad/zammad/issues/1769
  17. context 'when matching multiple tickets, each with multiple articles' do
  18. let(:tickets) { create_list(:ticket, 2) }
  19. before do
  20. create(:ticket_article, ticket: tickets.first, from: 'asdf1@blubselector.de')
  21. create(:ticket_article, ticket: tickets.first, from: 'asdf2@blubselector.de')
  22. create(:ticket_article, ticket: tickets.first, from: 'asdf3@blubselector.de')
  23. create(:ticket_article, ticket: tickets.last, from: 'asdf4@blubselector.de')
  24. create(:ticket_article, ticket: tickets.last, from: 'asdf5@blubselector.de')
  25. create(:ticket_article, ticket: tickets.last, from: 'asdf6@blubselector.de')
  26. end
  27. let(:condition) do
  28. {
  29. 'article.from' => {
  30. operator: 'contains',
  31. value: 'blubselector.de',
  32. },
  33. }
  34. end
  35. it 'returns a list of unique tickets (i.e., no duplicates)' do
  36. expect(Ticket.selectors(condition, limit: 100, access: 'full'))
  37. .to match_array([2, tickets.to_a])
  38. end
  39. end
  40. end
  41. end
  42. describe 'Instance methods:' do
  43. describe '#merge_to' do
  44. let(:target_ticket) { create(:ticket) }
  45. context 'when source ticket has Links' do
  46. let(:linked_tickets) { create_list(:ticket, 3) }
  47. let(:links) { linked_tickets.map { |l| create(:link, from: ticket, to: l) } }
  48. it 'reassigns all links to the target ticket after merge' do
  49. expect { ticket.merge_to(ticket_id: target_ticket.id, user_id: 1) }
  50. .to change { links.each(&:reload).map(&:link_object_source_value) }
  51. .to(Array.new(3) { target_ticket.id })
  52. end
  53. end
  54. context 'when attempting to cross-merge (i.e., to merge B → A after merging A → B)' do
  55. before { target_ticket.merge_to(ticket_id: ticket.id, user_id: 1) }
  56. it 'raises an error' do
  57. expect { ticket.merge_to(ticket_id: target_ticket.id, user_id: 1) }
  58. .to raise_error('ticket already merged, no merge into merged ticket possible')
  59. end
  60. end
  61. context 'when attempting to self-merge (i.e., to merge A → A)' do
  62. it 'raises an error' do
  63. expect { ticket.merge_to(ticket_id: ticket.id, user_id: 1) }
  64. .to raise_error("Can't merge ticket with it self!")
  65. end
  66. end
  67. # Issue #2469 - Add information "Ticket merged" to History
  68. context 'when merging' do
  69. let(:merge_user) { create(:user) }
  70. it 'creates history entries in both the origin ticket and the target ticket' do
  71. ticket.merge_to(ticket_id: target_ticket.id, user_id: merge_user.id)
  72. expect(target_ticket.history_get.size).to eq 2
  73. target_history = target_ticket.history_get.last
  74. expect(target_history['object']).to eq 'Ticket'
  75. expect(target_history['type']).to eq 'received_merge'
  76. expect(target_history['created_by_id']).to eq merge_user.id
  77. expect(target_history['o_id']).to eq target_ticket.id
  78. expect(target_history['id_to']).to eq target_ticket.id
  79. expect(target_history['id_from']).to eq ticket.id
  80. expect(ticket.history_get.size).to eq 4
  81. origin_history = ticket.reload.history_get[1]
  82. expect(origin_history['object']).to eq 'Ticket'
  83. expect(origin_history['type']).to eq 'merged_into'
  84. expect(origin_history['created_by_id']).to eq merge_user.id
  85. expect(origin_history['o_id']).to eq ticket.id
  86. expect(origin_history['id_to']).to eq target_ticket.id
  87. expect(origin_history['id_from']).to eq ticket.id
  88. end
  89. end
  90. end
  91. describe '#perform_changes' do
  92. # Regression test for https://github.com/zammad/zammad/issues/2001
  93. describe 'argument handling' do
  94. let(:perform) do
  95. {
  96. 'notification.email' => {
  97. body: "Hello \#{ticket.customer.firstname} \#{ticket.customer.lastname},",
  98. recipient: %w[article_last_sender ticket_owner ticket_customer ticket_agents],
  99. subject: "Autoclose (\#{ticket.title})"
  100. }
  101. }
  102. end
  103. it 'does not mutate contents of "perform" hash' do
  104. expect { ticket.perform_changes(perform, 'trigger', {}, 1) }
  105. .not_to change { perform }
  106. end
  107. end
  108. context 'with "ticket.state_id" key in "perform" hash' do
  109. let(:perform) do
  110. {
  111. 'ticket.state_id' => {
  112. 'value' => Ticket::State.lookup(name: 'closed').id
  113. }
  114. }
  115. end
  116. it 'changes #state to specified value' do
  117. expect { ticket.perform_changes(perform, 'trigger', ticket, User.first) }
  118. .to change { ticket.reload.state.name }.to('closed')
  119. end
  120. end
  121. context 'with "ticket.action" => { "value" => "delete" } in "perform" hash' do
  122. let(:perform) do
  123. {
  124. 'ticket.state_id' => { 'value' => Ticket::State.lookup(name: 'closed').id.to_s },
  125. 'ticket.action' => { 'value' => 'delete' },
  126. }
  127. end
  128. it 'performs a ticket deletion on a ticket' do
  129. expect { ticket.perform_changes(perform, 'trigger', ticket, User.first) }
  130. .to change { ticket.destroyed? }.to(true)
  131. end
  132. end
  133. context 'with a "notification.email" trigger' do
  134. # Regression test for https://github.com/zammad/zammad/issues/1543
  135. #
  136. # If a new article fires an email notification trigger,
  137. # and then another article is added to the same ticket
  138. # before that trigger is performed,
  139. # the email template's 'article' var should refer to the originating article,
  140. # not the newest one.
  141. #
  142. # (This occurs whenever one action fires multiple email notification triggers.)
  143. context 'when two articles are created before the trigger fires once (race condition)' do
  144. let!(:article) { create(:ticket_article, ticket: ticket) }
  145. let!(:new_article) { create(:ticket_article, ticket: ticket) }
  146. let(:trigger) do
  147. build(:trigger,
  148. perform: {
  149. 'notification.email' => {
  150. body: '',
  151. recipient: 'ticket_customer',
  152. subject: ''
  153. }
  154. })
  155. end
  156. # required by Ticket#perform_changes for email notifications
  157. before { article.ticket.group.update(email_address: create(:email_address)) }
  158. it 'passes the first article to NotificationFactory::Mailer' do
  159. expect(NotificationFactory::Mailer)
  160. .to receive(:template)
  161. .with(hash_including(objects: { ticket: ticket, article: article }))
  162. .at_least(:once)
  163. .and_call_original
  164. expect(NotificationFactory::Mailer)
  165. .not_to receive(:template)
  166. .with(hash_including(objects: { ticket: ticket, article: new_article }))
  167. ticket.perform_changes(trigger.perform, 'trigger', { article_id: article.id }, 1)
  168. end
  169. end
  170. end
  171. end
  172. describe '#access?' do
  173. context 'when given ticket’s owner' do
  174. it 'returns true for both "read" and "full" privileges' do
  175. expect(ticket.access?(ticket.owner, 'read')).to be(true)
  176. expect(ticket.access?(ticket.owner, 'full')).to be(true)
  177. end
  178. end
  179. context 'when given the ticket’s customer' do
  180. it 'returns true for both "read" and "full" privileges' do
  181. expect(ticket.access?(ticket.customer, 'read')).to be(true)
  182. expect(ticket.access?(ticket.customer, 'full')).to be(true)
  183. end
  184. end
  185. context 'when given a user that is neither owner nor customer' do
  186. let(:user) { create(:agent_user) }
  187. it 'returns false for both "read" and "full" privileges' do
  188. expect(ticket.access?(user, 'read')).to be(false)
  189. expect(ticket.access?(user, 'full')).to be(false)
  190. end
  191. context 'but the user is an agent with full access to ticket’s group' do
  192. before { user.group_names_access_map = { ticket.group.name => 'full' } }
  193. it 'returns true for both "read" and "full" privileges' do
  194. expect(ticket.access?(user, 'read')).to be(true)
  195. expect(ticket.access?(user, 'full')).to be(true)
  196. end
  197. end
  198. context 'but the user is a customer from the same organization as ticket’s customer' do
  199. subject(:ticket) { create(:ticket, customer: customer) }
  200. let(:customer) { create(:customer_user, organization: create(:organization)) }
  201. let(:colleague) { create(:customer_user, organization: customer.organization) }
  202. context 'and organization.shared is true (default)' do
  203. it 'returns true for both "read" and "full" privileges' do
  204. expect(ticket.access?(colleague, 'read')).to be(true)
  205. expect(ticket.access?(colleague, 'full')).to be(true)
  206. end
  207. end
  208. context 'but organization.shared is false' do
  209. before { customer.organization.update(shared: false) }
  210. it 'returns false for both "read" and "full" privileges' do
  211. expect(ticket.access?(colleague, 'read')).to be(false)
  212. expect(ticket.access?(colleague, 'full')).to be(false)
  213. end
  214. end
  215. end
  216. end
  217. end
  218. end
  219. describe 'Attributes:' do
  220. describe '#owner' do
  221. let(:original_owner) { create(:agent_user, groups: [ticket.group]) }
  222. before { ticket.update(owner: original_owner) }
  223. context 'when assigned directly' do
  224. context 'to an active agent belonging to ticket.group' do
  225. let(:agent) { create(:agent_user, groups: [ticket.group]) }
  226. it 'can be set' do
  227. expect { ticket.update(owner: agent) }
  228. .to change { ticket.reload.owner }.to(agent)
  229. end
  230. end
  231. context 'to an agent not belonging to ticket.group' do
  232. let(:agent) { create(:agent_user, groups: [other_group]) }
  233. let(:other_group) { create(:group) }
  234. it 'resets to default user (id: 1) instead' do
  235. expect { ticket.update(owner: agent) }
  236. .to change { ticket.reload.owner }.to(User.first)
  237. end
  238. end
  239. context 'to an inactive agent' do
  240. let(:agent) { create(:agent_user, groups: [ticket.group], active: false) }
  241. it 'resets to default user (id: 1) instead' do
  242. expect { ticket.update(owner: agent) }
  243. .to change { ticket.reload.owner }.to(User.first)
  244. end
  245. end
  246. context 'to a non-agent' do
  247. let(:agent) { create(:customer_user, groups: [ticket.group]) }
  248. it 'resets to default user (id: 1) instead' do
  249. expect { ticket.update(owner: agent) }
  250. .to change { ticket.reload.owner }.to(User.first)
  251. end
  252. end
  253. end
  254. context 'when the ticket is updated for any other reason' do
  255. context 'if original owner is still an active agent belonging to ticket.group' do
  256. it 'does not change' do
  257. expect { create(:ticket_article, ticket: ticket) }
  258. .not_to change { ticket.reload.owner }
  259. end
  260. end
  261. context 'if original owner has left ticket.group' do
  262. before { original_owner.groups = [] }
  263. it 'resets to default user (id: 1)' do
  264. expect { create(:ticket_article, ticket: ticket) }
  265. .to change { ticket.reload.owner }.to(User.first)
  266. end
  267. end
  268. context 'if original owner has become inactive' do
  269. before { original_owner.update(active: false) }
  270. it 'resets to default user (id: 1)' do
  271. expect { create(:ticket_article, ticket: ticket) }
  272. .to change { ticket.reload.owner }.to(User.first)
  273. end
  274. end
  275. context 'if original owner has lost agent status' do
  276. before { original_owner.roles = [create(:role)] }
  277. it 'resets to default user (id: 1)' do
  278. expect { create(:ticket_article, ticket: ticket) }
  279. .to change { ticket.reload.owner }.to(User.first)
  280. end
  281. end
  282. end
  283. end
  284. describe '#state' do
  285. context 'when originally "new" (default)' do
  286. context 'and a customer article is added' do
  287. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Customer') }
  288. it 'stays "new"' do
  289. expect { article }
  290. .not_to change { ticket.state.name }.from('new')
  291. end
  292. end
  293. context 'and a non-customer article is added' do
  294. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  295. it 'switches to "open"' do
  296. expect { article }
  297. .to change { ticket.state.name }.from('new').to('open')
  298. end
  299. end
  300. end
  301. context 'when originally "closed"' do
  302. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  303. context 'when a non-customer article is added' do
  304. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  305. it 'stays "closed"' do
  306. expect { article }.not_to change { ticket.state.name }
  307. end
  308. end
  309. end
  310. end
  311. describe '#pending_time' do
  312. subject(:ticket) { create(:ticket, pending_time: Time.zone.now + 2.days) }
  313. context 'when #state is updated to any non-"pending" value' do
  314. it 'is reset to nil' do
  315. expect { ticket.update!(state: Ticket::State.lookup(name: 'open')) }
  316. .to change { ticket.pending_time }.to(nil)
  317. end
  318. end
  319. # Regression test for commit 92f227786f298bad1ccaf92d4478a7062ea6a49f
  320. context 'when #state is updated to nil (violating DB NOT NULL constraint)' do
  321. it 'does not prematurely raise within the callback (#reset_pending_time)' do
  322. expect { ticket.update!(state: nil) }
  323. .to raise_error(ActiveRecord::StatementInvalid)
  324. end
  325. end
  326. end
  327. describe '#escalation_at' do
  328. before { travel_to(Time.current) } # freeze time
  329. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  330. let(:calendar) { create(:calendar, :'24/7') }
  331. context 'with no SLAs in the system' do
  332. it 'defaults to nil' do
  333. expect(ticket.escalation_at).to be(nil)
  334. end
  335. end
  336. context 'with an SLA in the system' do
  337. before { sla } # create sla
  338. it 'is set based on SLA’s #first_response_time' do
  339. expect(ticket.escalation_at.to_i)
  340. .to eq(1.hour.from_now.to_i)
  341. end
  342. context 'after first agent’s response' do
  343. before { ticket } # create ticket
  344. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  345. it 'is updated based on the SLA’s #update_time' do
  346. travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  347. expect { article }
  348. .to change { ticket.reload.escalation_at.to_i }
  349. .to eq(3.hours.from_now.to_i)
  350. end
  351. context 'when new #update_time is later than original #solution_time' do
  352. it 'is updated based on the original #solution_time' do
  353. travel(2.hours) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  354. expect { article }
  355. .to change { ticket.reload.escalation_at.to_i }
  356. .to eq(4.hours.after(ticket.created_at).to_i)
  357. end
  358. end
  359. end
  360. end
  361. context 'when updated after an SLA has been added to the system' do
  362. before { ticket } # create ticket
  363. before { sla } # create sla
  364. it 'is updated based on the new SLA’s #first_response_time' do
  365. expect { ticket.save! }
  366. .to change { ticket.escalation_at.to_i }.from(0).to(1.hour.from_now.to_i)
  367. end
  368. end
  369. context 'when updated after all SLAs have been removed from the system' do
  370. before { sla } # create sla
  371. before { ticket } # create ticket
  372. before { sla.destroy }
  373. it 'is set to nil' do
  374. expect { ticket.save! }
  375. .to change { ticket.escalation_at }.to(nil)
  376. end
  377. end
  378. end
  379. describe '#first_response_escalation_at' do
  380. before { travel_to(Time.current) } # freeze time
  381. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  382. let(:calendar) { create(:calendar, :'24/7') }
  383. context 'with no SLAs in the system' do
  384. it 'defaults to nil' do
  385. expect(ticket.first_response_escalation_at).to be(nil)
  386. end
  387. end
  388. context 'with an SLA in the system' do
  389. before { sla } # create sla
  390. it 'is set based on SLA’s #first_response_time' do
  391. expect(ticket.first_response_escalation_at.to_i)
  392. .to eq(1.hour.from_now.to_i)
  393. end
  394. context 'after first agent’s response' do
  395. before { ticket } # create ticket
  396. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  397. it 'does not change' do
  398. expect { article }.not_to change { ticket.first_response_escalation_at }
  399. end
  400. end
  401. end
  402. end
  403. describe '#update_escalation_at' do
  404. before { travel_to(Time.current) } # freeze time
  405. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  406. let(:calendar) { create(:calendar, :'24/7') }
  407. context 'with no SLAs in the system' do
  408. it 'defaults to nil' do
  409. expect(ticket.update_escalation_at).to be(nil)
  410. end
  411. end
  412. context 'with an SLA in the system' do
  413. before { sla } # create sla
  414. it 'is set based on SLA’s #update_time' do
  415. expect(ticket.update_escalation_at.to_i)
  416. .to eq(3.hours.from_now.to_i)
  417. end
  418. context 'after first agent’s response' do
  419. before { ticket } # create ticket
  420. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  421. it 'is updated based on the SLA’s #update_time' do
  422. travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  423. expect { article }
  424. .to change { ticket.reload.update_escalation_at.to_i }
  425. .to(3.hours.from_now.to_i)
  426. end
  427. end
  428. end
  429. end
  430. describe '#close_escalation_at' do
  431. before { travel_to(Time.current) } # freeze time
  432. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  433. let(:calendar) { create(:calendar, :'24/7') }
  434. context 'with no SLAs in the system' do
  435. it 'defaults to nil' do
  436. expect(ticket.close_escalation_at).to be(nil)
  437. end
  438. end
  439. context 'with an SLA in the system' do
  440. before { sla } # create sla
  441. it 'is set based on SLA’s #solution_time' do
  442. expect(ticket.close_escalation_at.to_i)
  443. .to eq(4.hours.from_now.to_i)
  444. end
  445. context 'after first agent’s response' do
  446. before { ticket } # create ticket
  447. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  448. it 'does not change' do
  449. expect { article }.not_to change { ticket.close_escalation_at }
  450. end
  451. end
  452. end
  453. end
  454. end
  455. describe 'Associations:' do
  456. describe '#organization' do
  457. subject(:ticket) { build(:ticket, customer: customer, organization: nil) }
  458. let(:customer) { create(:customer, :with_org) }
  459. context 'on creation' do
  460. it 'automatically adopts the organization of its #customer' do
  461. expect { ticket.save }
  462. .to change { ticket.organization }.to(customer.organization)
  463. end
  464. end
  465. context 'on update of #customer.organization' do
  466. context 'to nil' do
  467. it 'automatically updates to #customer’s new value' do
  468. ticket.save
  469. expect { customer.update(organization: nil) }
  470. .to change { ticket.reload.organization }.to(nil)
  471. end
  472. end
  473. context 'to a different organization' do
  474. let(:new_org) { create(:organization) }
  475. it 'automatically updates to #customer’s new value' do
  476. ticket.save
  477. expect { customer.update(organization: new_org) }
  478. .to change { ticket.reload.organization }.to(new_org)
  479. end
  480. end
  481. end
  482. end
  483. end
  484. describe 'Callbacks & Observers -' do
  485. describe 'NULL byte handling (via ChecksAttributeValuesAndLength concern):' do
  486. it 'removes them from title on creation, if necessary (postgres doesn’t like them)' do
  487. expect { create(:ticket, title: "some title \u0000 123") }
  488. .not_to raise_error
  489. end
  490. end
  491. describe 'XSS protection:' do
  492. subject(:ticket) { create(:ticket, title: title) }
  493. let(:title) { 'test 123 <script type="text/javascript">alert("XSS!");</script>' }
  494. it 'does not sanitize title' do
  495. expect(ticket.title).to eq(title)
  496. end
  497. end
  498. describe 'Cti::CallerId syncing:' do
  499. subject(:ticket) { build(:ticket) }
  500. before { allow(Cti::CallerId).to receive(:build) }
  501. it 'adds numbers in article bodies (via Cti::CallerId.build)' do
  502. expect(Cti::CallerId).to receive(:build).with(ticket)
  503. ticket.save
  504. Observer::Transaction.commit
  505. Scheduler.worker(true)
  506. end
  507. end
  508. describe 'Touching associations on update:' do
  509. subject(:ticket) { create(:ticket, customer: customer) }
  510. let(:customer) { create(:customer_user, organization: organization) }
  511. let(:organization) { create(:organization) }
  512. let(:other_customer) { create(:customer_user, organization: other_organization) }
  513. let(:other_organization) { create(:organization) }
  514. context 'on creation' do
  515. it 'touches its customer and his organization' do
  516. expect { ticket }
  517. .to change { customer.reload.updated_at }
  518. .and change { organization.reload.updated_at }
  519. end
  520. end
  521. context 'on destruction' do
  522. before { ticket }
  523. it 'touches its customer and his organization' do
  524. expect { ticket.destroy }
  525. .to change { customer.reload.updated_at }
  526. .and change { organization.reload.updated_at }
  527. end
  528. end
  529. context 'when customer association is changed' do
  530. it 'touches both old and new customer, and their organizations' do
  531. expect { ticket.update(customer: other_customer) }
  532. .to change { customer.reload.updated_at }
  533. .and change { organization.reload.updated_at }
  534. .and change { other_customer.reload.updated_at }
  535. .and change { other_organization.reload.updated_at }
  536. end
  537. end
  538. context 'when organization has 100+ members' do
  539. let!(:other_members) { create_list(:user, 100, organization: organization) }
  540. context 'and customer association is changed' do
  541. it 'touches both old and new customer, and their organizations' do
  542. expect { ticket.update(customer: other_customer) }
  543. .to change { customer.reload.updated_at }
  544. .and change { organization.reload.updated_at }
  545. .and change { other_customer.reload.updated_at }
  546. .and change { other_organization.reload.updated_at }
  547. end
  548. end
  549. end
  550. end
  551. describe 'Association & attachment management:' do
  552. it 'deletes all related ActivityStreams on destroy' do
  553. create_list(:activity_stream, 3, o: ticket)
  554. expect { ticket.destroy }
  555. .to change { ActivityStream.exists?(activity_stream_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  556. .to(false)
  557. end
  558. it 'deletes all related Links on destroy' do
  559. create(:link, from: ticket, to: create(:ticket))
  560. create(:link, from: create(:ticket), to: ticket)
  561. create(:link, from: ticket, to: create(:ticket))
  562. expect { ticket.destroy }
  563. .to change { Link.where('link_object_source_value = :id OR link_object_target_value = :id', id: ticket.id).any? }
  564. .to(false)
  565. end
  566. it 'deletes all related Articles on destroy' do
  567. create_list(:ticket_article, 3, ticket: ticket)
  568. expect { ticket.destroy }
  569. .to change { Ticket::Article.exists?(ticket: ticket) }
  570. .to(false)
  571. end
  572. it 'deletes all related OnlineNotifications on destroy' do
  573. create_list(:online_notification, 3, o: ticket)
  574. expect { ticket.destroy }
  575. .to change { OnlineNotification.where(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id).any? }
  576. .to(false)
  577. end
  578. it 'deletes all related Tags on destroy' do
  579. create_list(:tag, 3, o: ticket)
  580. expect { ticket.destroy }
  581. .to change { Tag.exists?(tag_object_id: Tag::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  582. .to(false)
  583. end
  584. it 'deletes all related Histories on destroy' do
  585. create_list(:history, 3, o: ticket)
  586. expect { ticket.destroy }
  587. .to change { History.exists?(history_object_id: History::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  588. .to(false)
  589. end
  590. it 'deletes all related Karma::ActivityLogs on destroy' do
  591. create_list(:'karma/activity_log', 3, o: ticket)
  592. expect { ticket.destroy }
  593. .to change { Karma::ActivityLog.exists?(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  594. .to(false)
  595. end
  596. it 'deletes all related RecentViews on destroy' do
  597. create_list(:recent_view, 3, o: ticket)
  598. expect { ticket.destroy }
  599. .to change { RecentView.exists?(recent_view_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  600. .to(false)
  601. end
  602. context 'when ticket is generated from email (with attachments)' do
  603. subject(:ticket) { Channel::EmailParser.new.process({}, raw_email).first }
  604. let(:raw_email) { File.read(Rails.root.join('test', 'data', 'mail', 'mail001.box')) }
  605. it 'adds attachments to the Store{::File,::Provider::DB} tables' do
  606. expect { ticket }
  607. .to change { Store.count }.by(2)
  608. .and change { Store::File.count }.by(2)
  609. .and change { Store::Provider::DB.count }.by(2)
  610. end
  611. context 'and subsequently destroyed' do
  612. it 'deletes all related attachments' do
  613. ticket # create ticket
  614. expect { ticket.destroy }
  615. .to change { Store.count }.by(-2)
  616. .and change { Store::File.count }.by(-2)
  617. .and change { Store::Provider::DB.count }.by(-2)
  618. end
  619. end
  620. context 'and a duplicate ticket is generated from the same email' do
  621. before { ticket } # create ticket
  622. let(:duplicate) { Channel::EmailParser.new.process({}, raw_email).first }
  623. it 'adds duplicate attachments to the Store table only' do
  624. expect { duplicate }
  625. .to change { Store.count }.by(2)
  626. .and change { Store::File.count }.by(0)
  627. .and change { Store::Provider::DB.count }.by(0)
  628. end
  629. context 'when only the duplicate ticket is destroyed' do
  630. it 'deletes only the duplicate attachments' do
  631. duplicate # create ticket
  632. expect { duplicate.destroy }
  633. .to change { Store.count }.by(-2)
  634. .and change { Store::File.count }.by(0)
  635. .and change { Store::Provider::DB.count }.by(0)
  636. end
  637. end
  638. context 'when only the duplicate ticket is destroyed' do
  639. it 'deletes all related attachments' do
  640. duplicate.destroy
  641. expect { ticket.destroy }
  642. .to change { Store.count }.by(-2)
  643. .and change { Store::File.count }.by(-2)
  644. .and change { Store::Provider::DB.count }.by(-2)
  645. end
  646. end
  647. end
  648. end
  649. end
  650. end
  651. end