ticket_spec.rb 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  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"' do
  286. context 'and a non-customer article is added' do
  287. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  288. it 'switches to "open"' do
  289. expect { article }.to change { ticket.state.name }.from('new').to('open')
  290. end
  291. end
  292. end
  293. context 'when originally "closed"' do
  294. before { ticket.update(state: Ticket::State.find_by(name: 'closed')) }
  295. context 'when a non-customer article is added' do
  296. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  297. it 'stays "closed"' do
  298. expect { article }.not_to change { ticket.state.name }
  299. end
  300. end
  301. end
  302. end
  303. describe '#pending_time' do
  304. subject(:ticket) { create(:ticket, pending_time: Time.zone.now + 2.days) }
  305. context 'when #state is updated to any non-"pending" value' do
  306. it 'is reset to nil' do
  307. expect { ticket.update!(state: Ticket::State.lookup(name: 'open')) }
  308. .to change { ticket.pending_time }.to(nil)
  309. end
  310. end
  311. # Regression test for commit 92f227786f298bad1ccaf92d4478a7062ea6a49f
  312. context 'when #state is updated to nil (violating DB NOT NULL constraint)' do
  313. it 'does not prematurely raise within the callback (#reset_pending_time)' do
  314. expect { ticket.update!(state: nil) }
  315. .to raise_error(ActiveRecord::StatementInvalid)
  316. end
  317. end
  318. end
  319. describe '#escalation_at' do
  320. before { travel_to(Time.current) } # freeze time
  321. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  322. let(:calendar) { create(:calendar, :'24/7') }
  323. context 'with no SLAs in the system' do
  324. it 'defaults to nil' do
  325. expect(ticket.escalation_at).to be(nil)
  326. end
  327. end
  328. context 'with an SLA in the system' do
  329. before { sla } # create sla
  330. it 'is set based on SLA’s #first_response_time' do
  331. expect(ticket.escalation_at.to_i)
  332. .to eq(1.hour.from_now.to_i)
  333. end
  334. context 'after first agent’s response' do
  335. before { ticket } # create ticket
  336. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  337. it 'is updated based on the SLA’s #update_time' do
  338. travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  339. expect { article }
  340. .to change { ticket.reload.escalation_at.to_i }
  341. .to eq(3.hours.from_now.to_i)
  342. end
  343. context 'when new #update_time is later than original #solution_time' do
  344. it 'is updated based on the original #solution_time' do
  345. travel(2.hours) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  346. expect { article }
  347. .to change { ticket.reload.escalation_at.to_i }
  348. .to eq(4.hours.after(ticket.created_at).to_i)
  349. end
  350. end
  351. end
  352. end
  353. context 'when updated after an SLA has been added to the system' do
  354. before { ticket } # create ticket
  355. before { sla } # create sla
  356. it 'is updated based on the new SLA’s #first_response_time' do
  357. expect { ticket.save! }
  358. .to change { ticket.escalation_at.to_i }.from(0).to(1.hour.from_now.to_i)
  359. end
  360. end
  361. context 'when updated after all SLAs have been removed from the system' do
  362. before { sla } # create sla
  363. before { ticket } # create ticket
  364. before { sla.destroy }
  365. it 'is set to nil' do
  366. expect { ticket.save! }
  367. .to change { ticket.escalation_at }.to(nil)
  368. end
  369. end
  370. end
  371. describe '#first_response_escalation_at' do
  372. before { travel_to(Time.current) } # freeze time
  373. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  374. let(:calendar) { create(:calendar, :'24/7') }
  375. context 'with no SLAs in the system' do
  376. it 'defaults to nil' do
  377. expect(ticket.first_response_escalation_at).to be(nil)
  378. end
  379. end
  380. context 'with an SLA in the system' do
  381. before { sla } # create sla
  382. it 'is set based on SLA’s #first_response_time' do
  383. expect(ticket.first_response_escalation_at.to_i)
  384. .to eq(1.hour.from_now.to_i)
  385. end
  386. context 'after first agent’s response' do
  387. before { ticket } # create ticket
  388. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  389. it 'does not change' do
  390. expect { article }.not_to change { ticket.first_response_escalation_at }
  391. end
  392. end
  393. end
  394. end
  395. describe '#update_escalation_at' do
  396. before { travel_to(Time.current) } # freeze time
  397. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  398. let(:calendar) { create(:calendar, :'24/7') }
  399. context 'with no SLAs in the system' do
  400. it 'defaults to nil' do
  401. expect(ticket.update_escalation_at).to be(nil)
  402. end
  403. end
  404. context 'with an SLA in the system' do
  405. before { sla } # create sla
  406. it 'is set based on SLA’s #update_time' do
  407. expect(ticket.update_escalation_at.to_i)
  408. .to eq(3.hours.from_now.to_i)
  409. end
  410. context 'after first agent’s response' do
  411. before { ticket } # create ticket
  412. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  413. it 'is updated based on the SLA’s #update_time' do
  414. travel(1.minute) # time is frozen: if we don't travel forward, pre- and post-update values will be the same
  415. expect { article }
  416. .to change { ticket.reload.update_escalation_at.to_i }
  417. .to(3.hours.from_now.to_i)
  418. end
  419. end
  420. end
  421. end
  422. describe '#close_escalation_at' do
  423. before { travel_to(Time.current) } # freeze time
  424. let(:sla) { create(:sla, calendar: calendar, first_response_time: 60, update_time: 180, solution_time: 240) }
  425. let(:calendar) { create(:calendar, :'24/7') }
  426. context 'with no SLAs in the system' do
  427. it 'defaults to nil' do
  428. expect(ticket.close_escalation_at).to be(nil)
  429. end
  430. end
  431. context 'with an SLA in the system' do
  432. before { sla } # create sla
  433. it 'is set based on SLA’s #solution_time' do
  434. expect(ticket.close_escalation_at.to_i)
  435. .to eq(4.hours.from_now.to_i)
  436. end
  437. context 'after first agent’s response' do
  438. before { ticket } # create ticket
  439. let(:article) { create(:ticket_article, ticket: ticket, sender_name: 'Agent') }
  440. it 'does not change' do
  441. expect { article }.not_to change { ticket.close_escalation_at }
  442. end
  443. end
  444. end
  445. end
  446. end
  447. describe 'Associations:' do
  448. describe '#organization' do
  449. subject(:ticket) { build(:ticket, customer: customer, organization: nil) }
  450. let(:customer) { create(:customer, :with_org) }
  451. context 'on creation' do
  452. it 'automatically adopts the organization of its #customer' do
  453. expect { ticket.save }
  454. .to change { ticket.organization }.to(customer.organization)
  455. end
  456. end
  457. context 'on update of #customer.organization' do
  458. context 'to nil' do
  459. it 'automatically updates to #customer’s new value' do
  460. ticket.save
  461. expect { customer.update(organization: nil) }
  462. .to change { ticket.reload.organization }.to(nil)
  463. end
  464. end
  465. context 'to a different organization' do
  466. let(:new_org) { create(:organization) }
  467. it 'automatically updates to #customer’s new value' do
  468. ticket.save
  469. expect { customer.update(organization: new_org) }
  470. .to change { ticket.reload.organization }.to(new_org)
  471. end
  472. end
  473. end
  474. end
  475. end
  476. describe 'Callbacks & Observers -' do
  477. describe 'NULL byte handling (via ChecksAttributeValuesAndLength concern):' do
  478. it 'removes them from title on creation, if necessary (postgres doesn’t like them)' do
  479. expect { create(:ticket, title: "some title \u0000 123") }
  480. .not_to raise_error
  481. end
  482. end
  483. describe 'Cti::CallerId syncing:' do
  484. subject(:ticket) { build(:ticket) }
  485. before { allow(Cti::CallerId).to receive(:build) }
  486. it 'adds numbers in article bodies (via Cti::CallerId.build)' do
  487. expect(Cti::CallerId).to receive(:build).with(ticket)
  488. ticket.save
  489. Observer::Transaction.commit
  490. Scheduler.worker(true)
  491. end
  492. end
  493. describe 'Touching associations on update:' do
  494. subject(:ticket) { create(:ticket, customer: customer) }
  495. let(:customer) { create(:customer_user, organization: organization) }
  496. let(:organization) { create(:organization) }
  497. let(:other_customer) { create(:customer_user, organization: other_organization) }
  498. let(:other_organization) { create(:organization) }
  499. context 'on creation' do
  500. it 'touches its customer and his organization' do
  501. expect { ticket }
  502. .to change { customer.reload.updated_at }
  503. .and change { organization.reload.updated_at }
  504. end
  505. end
  506. context 'on destruction' do
  507. before { ticket }
  508. it 'touches its customer and his organization' do
  509. expect { ticket.destroy }
  510. .to change { customer.reload.updated_at }
  511. .and change { organization.reload.updated_at }
  512. end
  513. end
  514. context 'when customer association is changed' do
  515. it 'touches both old and new customer, and their organizations' do
  516. expect { ticket.update(customer: other_customer) }
  517. .to change { customer.reload.updated_at }
  518. .and change { organization.reload.updated_at }
  519. .and change { other_customer.reload.updated_at }
  520. .and change { other_organization.reload.updated_at }
  521. end
  522. end
  523. context 'when organization has 100+ members' do
  524. let!(:other_members) { create_list(:user, 100, organization: organization) }
  525. context 'and customer association is changed' do
  526. it 'touches both old and new customer, and their organizations' do
  527. expect { ticket.update(customer: other_customer) }
  528. .to change { customer.reload.updated_at }
  529. .and change { organization.reload.updated_at }
  530. .and change { other_customer.reload.updated_at }
  531. .and change { other_organization.reload.updated_at }
  532. end
  533. end
  534. end
  535. end
  536. describe 'Association & attachment management:' do
  537. it 'deletes all related ActivityStreams on destroy' do
  538. create_list(:activity_stream, 3, o: ticket)
  539. expect { ticket.destroy }
  540. .to change { ActivityStream.exists?(activity_stream_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  541. .to(false)
  542. end
  543. it 'deletes all related Links on destroy' do
  544. create(:link, from: ticket, to: create(:ticket))
  545. create(:link, from: create(:ticket), to: ticket)
  546. create(:link, from: ticket, to: create(:ticket))
  547. expect { ticket.destroy }
  548. .to change { Link.where('link_object_source_value = :id OR link_object_target_value = :id', id: ticket.id).any? }
  549. .to(false)
  550. end
  551. it 'deletes all related Articles on destroy' do
  552. create_list(:ticket_article, 3, ticket: ticket)
  553. expect { ticket.destroy }
  554. .to change { Ticket::Article.exists?(ticket: ticket) }
  555. .to(false)
  556. end
  557. it 'deletes all related OnlineNotifications on destroy' do
  558. create_list(:online_notification, 3, o: ticket)
  559. expect { ticket.destroy }
  560. .to change { OnlineNotification.where(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id).any? }
  561. .to(false)
  562. end
  563. it 'deletes all related Tags on destroy' do
  564. create_list(:tag, 3, o: ticket)
  565. expect { ticket.destroy }
  566. .to change { Tag.exists?(tag_object_id: Tag::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  567. .to(false)
  568. end
  569. it 'deletes all related Histories on destroy' do
  570. create_list(:history, 3, o: ticket)
  571. expect { ticket.destroy }
  572. .to change { History.exists?(history_object_id: History::Object.lookup(name: 'Ticket').id, o_id: ticket.id) }
  573. .to(false)
  574. end
  575. it 'deletes all related Karma::ActivityLogs on destroy' do
  576. create_list(:'karma/activity_log', 3, o: ticket)
  577. expect { ticket.destroy }
  578. .to change { Karma::ActivityLog.exists?(object_lookup_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  579. .to(false)
  580. end
  581. it 'deletes all related RecentViews on destroy' do
  582. create_list(:recent_view, 3, o: ticket)
  583. expect { ticket.destroy }
  584. .to change { RecentView.exists?(recent_view_object_id: ObjectLookup.by_name('Ticket'), o_id: ticket.id) }
  585. .to(false)
  586. end
  587. context 'when ticket is generated from email (with attachments)' do
  588. subject(:ticket) { Channel::EmailParser.new.process({}, raw_email).first }
  589. let(:raw_email) { File.read(Rails.root.join('test', 'data', 'mail', 'mail001.box')) }
  590. it 'adds attachments to the Store{::File,::Provider::DB} tables' do
  591. expect { ticket }
  592. .to change { Store.count }.by(2)
  593. .and change { Store::File.count }.by(2)
  594. .and change { Store::Provider::DB.count }.by(2)
  595. end
  596. context 'and subsequently destroyed' do
  597. it 'deletes all related attachments' do
  598. ticket # create ticket
  599. expect { ticket.destroy }
  600. .to change { Store.count }.by(-2)
  601. .and change { Store::File.count }.by(-2)
  602. .and change { Store::Provider::DB.count }.by(-2)
  603. end
  604. end
  605. context 'and a duplicate ticket is generated from the same email' do
  606. before { ticket } # create ticket
  607. let(:duplicate) { Channel::EmailParser.new.process({}, raw_email).first }
  608. it 'adds duplicate attachments to the Store table only' do
  609. expect { duplicate }
  610. .to change { Store.count }.by(2)
  611. .and change { Store::File.count }.by(0)
  612. .and change { Store::Provider::DB.count }.by(0)
  613. end
  614. context 'when only the duplicate ticket is destroyed' do
  615. it 'deletes only the duplicate attachments' do
  616. duplicate # create ticket
  617. expect { duplicate.destroy }
  618. .to change { Store.count }.by(-2)
  619. .and change { Store::File.count }.by(0)
  620. .and change { Store::Provider::DB.count }.by(0)
  621. end
  622. end
  623. context 'when only the duplicate ticket is destroyed' do
  624. it 'deletes all related attachments' do
  625. duplicate.destroy
  626. expect { ticket.destroy }
  627. .to change { Store.count }.by(-2)
  628. .and change { Store::File.count }.by(-2)
  629. .and change { Store::Provider::DB.count }.by(-2)
  630. end
  631. end
  632. end
  633. end
  634. end
  635. end
  636. end