article.rb 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. FactoryBot.define do
  3. factory :'ticket/article', aliases: %i[ticket_article] do
  4. inbound_email
  5. ticket factory: :ticket, strategy: :create # or else build(:ticket_article).save fails
  6. from { 'factory-customer-1@example.com' }
  7. to { 'factory-customer-1@example.com' }
  8. subject { 'factory article' }
  9. message_id { 'factory@id_com_1' }
  10. body { 'some message 123' }
  11. internal { false }
  12. sender { Ticket::Article::Sender.find_by(name: sender_name) }
  13. type { Ticket::Article::Type.find_by(name: type_name) }
  14. updated_by_id { 1 }
  15. created_by_id { 1 }
  16. trait :inbound_email do
  17. transient do
  18. type_name { 'email' }
  19. sender_name { 'Customer' }
  20. end
  21. end
  22. trait :outbound_email do
  23. transient do
  24. type_name { 'email' }
  25. sender_name { 'Agent' }
  26. end
  27. from { ticket.group.name }
  28. to { "#{ticket.customer.fullname} <#{ticket.customer.email}>" }
  29. end
  30. trait :inbound_phone do
  31. transient do
  32. type_name { 'phone' }
  33. sender_name { 'Customer' }
  34. end
  35. end
  36. trait :outbound_phone do
  37. transient do
  38. type_name { 'phone' }
  39. sender_name { 'Agent' }
  40. end
  41. from { nil }
  42. to { ticket.customer.fullname }
  43. end
  44. trait :outbound_note do
  45. transient do
  46. type_name { 'note' }
  47. sender_name { 'Agent' }
  48. end
  49. from { ticket.group.name }
  50. end
  51. trait :internal_note do
  52. transient do
  53. type_name { 'note' }
  54. sender_name { 'Agent' }
  55. end
  56. from { ticket.group.name }
  57. internal { true }
  58. end
  59. trait :inbound_web do
  60. transient do
  61. type_name { 'web' }
  62. sender_name { 'Customer' }
  63. end
  64. end
  65. trait :outbound_web do
  66. transient do
  67. type_name { 'web' }
  68. sender_name { 'Agent' }
  69. end
  70. end
  71. factory :twitter_article do
  72. transient do
  73. type_name { 'twitter status' }
  74. sender_name { 'Agent' }
  75. end
  76. ticket factory: %i[twitter_ticket]
  77. subject { nil }
  78. body { Faker::Lorem.sentence }
  79. content_type { 'text/plain' }
  80. message_id { Faker::Number.unique.number(digits: 18) }
  81. after(:create) do |article, context|
  82. next if context.sender_name == 'Agent'
  83. context.ticket.title = article.body
  84. context.ticket.save!
  85. end
  86. trait :inbound do
  87. transient do
  88. sender_name { 'Customer' }
  89. username { Faker::Twitter.screen_name }
  90. sender_id { Faker::Number.unique.number(digits: 18) }
  91. recipient_id { Faker::Number.unique.number(digits: 19) }
  92. end
  93. from { "@#{username}" }
  94. to { "@#{ticket.preferences['channel_screen_name']}" }
  95. body { "#{to} #{Faker::Lorem.question}" }
  96. preferences do
  97. {
  98. twitter: {
  99. mention_ids: [recipient_id],
  100. geo: {},
  101. retweeted: false,
  102. possibly_sensitive: false,
  103. in_reply_to_user_id: recipient_id,
  104. place: {},
  105. retweet_count: 0,
  106. source: '<a href="https://mobile.twitter.com" rel="nofollow">Twitter Web App</a>',
  107. favorited: false,
  108. truncated: false
  109. },
  110. links: [
  111. {
  112. url: "https://twitter.com/_/status/#{sender_id}",
  113. target: '_blank',
  114. name: 'on Twitter',
  115. },
  116. ],
  117. }
  118. end
  119. end
  120. trait :outbound do
  121. transient do
  122. username { Faker::Twitter.screen_name }
  123. sender_id { Faker::Number.unique.number(digits: 18) }
  124. recipient_id { Faker::Number.unique.number(digits: 19) }
  125. end
  126. from { "@#{ticket.preferences['channel_screen_name']}" }
  127. to { "@#{username}" }
  128. body { "#{to} #{Faker::Lorem.sentence}" }
  129. in_reply_to { Faker::Number.unique.number(digits: 19) }
  130. preferences do
  131. {
  132. twitter: {
  133. mention_ids: [recipient_id],
  134. geo: {},
  135. retweeted: false,
  136. possibly_sensitive: false,
  137. in_reply_to_user_id: recipient_id,
  138. place: {},
  139. retweet_count: 0,
  140. source: '<a href="https://www.canva.com" rel="nofollow">Canva</a>',
  141. favorited: false,
  142. truncated: false
  143. },
  144. links: [
  145. {
  146. url: "https://twitter.com/_/status/#{sender_id}",
  147. target: '_blank',
  148. name: 'on Twitter',
  149. },
  150. ],
  151. }
  152. end
  153. end
  154. trait :reply do
  155. in_reply_to { Faker::Number.unique.number(digits: 19) }
  156. end
  157. end
  158. factory :twitter_dm_article do
  159. transient do
  160. type_name { 'twitter direct-message' }
  161. end
  162. ticket factory: %i[twitter_ticket]
  163. body { Faker::Lorem.sentence }
  164. trait :pending_delivery do
  165. transient do
  166. recipient { association :twitter_authorization }
  167. sender_id { Faker::Number.unique.number(digits: 10) }
  168. end
  169. from { ticket.owner.fullname }
  170. to { recipient.username }
  171. in_reply_to { Faker::Number.unique.number(digits: 19) }
  172. content_type { 'text/plain' }
  173. end
  174. trait :delivered do
  175. pending_delivery
  176. message_id { Faker::Number.unique.number(digits: 19) }
  177. preferences do
  178. {
  179. delivery_retry: 1,
  180. twitter: {
  181. recipient_id: recipient.uid,
  182. sender_id: sender_id
  183. },
  184. links: [
  185. {
  186. url: "https://twitter.com/messages/#{recipient.uid}-#{sender_id}",
  187. target: '_blank',
  188. name: 'on Twitter'
  189. }
  190. ],
  191. delivery_status_message: nil,
  192. delivery_status: 'success',
  193. delivery_status_date: Time.current
  194. }
  195. end
  196. end
  197. end
  198. factory :sms_article do
  199. inbound
  200. transient do
  201. type_name { 'sms' }
  202. end
  203. ticket factory: %i[sms_ticket]
  204. from { Faker::PhoneNumber.cell_phone_in_e164 }
  205. to { Faker::PhoneNumber.cell_phone_in_e164 }
  206. subject { nil }
  207. body { Faker::Lorem.sentence }
  208. message_id { Faker::Number.unique.number(digits: 19) }
  209. content_type { 'text/plain' }
  210. after(:create) do |article, context|
  211. next if context.sender_name == 'Agent'
  212. context.ticket.title = article.body
  213. context.ticket.preferences.tap do |p|
  214. p['sms'] = {
  215. originator: article.from,
  216. recipient: article.to,
  217. }
  218. end
  219. context.ticket.save!
  220. end
  221. trait :inbound do
  222. transient do
  223. sender_name { 'Customer' }
  224. end
  225. preferences do
  226. {
  227. channel_id: ticket.preferences['channel_id'],
  228. sms: {
  229. reference: message_id,
  230. },
  231. }
  232. end
  233. end
  234. trait :outbound do
  235. transient do
  236. sender_name { 'Agent' }
  237. end
  238. in_reply_to { Faker::Number.unique.number(digits: 19) }
  239. preferences do
  240. {
  241. delivery_retry: 1,
  242. delivery_status_message: nil,
  243. delivery_status: 'success',
  244. delivery_status_date: Time.current,
  245. }
  246. end
  247. end
  248. end
  249. factory :whatsapp_article do
  250. inbound
  251. transient do
  252. type_name { 'whatsapp message' }
  253. channel { Channel.find(ticket.preferences[:channel_id]) }
  254. from_phone_number { Faker::PhoneNumber.cell_phone_in_e164 }
  255. from_name { Faker::Name.unique.name }
  256. timestamp_incoming { Time.zone.now.to_i.to_s }
  257. end
  258. ticket factory: %i[whatsapp_ticket]
  259. to { "#{channel.options[:name]} (#{channel.options[:phone_number]})" }
  260. subject { nil }
  261. body { Faker::Lorem.sentence }
  262. content_type { 'text/plain' }
  263. before(:create) do |_article, context|
  264. next if context.sender_name == 'Agent' && context.ticket.preferences[:whatsapp].present?
  265. context.ticket.preferences.tap do |p|
  266. p['whatsapp'] = {
  267. from: {
  268. phone_number: context.from_phone_number.delete('+'),
  269. display_name: context.from_name,
  270. },
  271. timestamp_incoming: context.timestamp_incoming,
  272. }
  273. end
  274. context.ticket.title = "New WhatsApp message from #{context.from_name} (#{context.from_phone_number})"
  275. context.ticket.save!
  276. end
  277. trait :inbound do
  278. transient do
  279. sender_name { 'Customer' }
  280. end
  281. message_id { "wamid.#{Faker::Number.unique.number}" }
  282. from { "#{from_name} (#{from_phone_number})" }
  283. created_by_id { ticket.customer_id } # NB: influences the value for the from field!
  284. preferences do
  285. {
  286. whatsapp: {
  287. entry_id: channel[:options][:phone_number_id],
  288. message_id: message_id,
  289. }
  290. }
  291. end
  292. end
  293. trait :pending_delivery do
  294. transient do
  295. sender_name { 'Agent' }
  296. end
  297. preferences { {} }
  298. created_by_id { create(:agent).id } # NB: influences the value for the from field!
  299. in_reply_to { "wamid.#{Faker::Number.unique.number}" }
  300. end
  301. trait :outbound do
  302. pending_delivery
  303. message_id { "wamid.#{Faker::Number.unique.number}" }
  304. preferences do
  305. {
  306. delivery_retry: 1,
  307. whatsapp: {
  308. message_id:,
  309. },
  310. delivery_status_message: nil,
  311. delivery_status: 'success',
  312. delivery_status_date: Time.current,
  313. }
  314. end
  315. end
  316. trait :with_attachment_media_document do
  317. after(:create) do |article, _context|
  318. create(:store,
  319. object: article.class.name,
  320. o_id: article.id,
  321. data: Faker::Lorem.unique.sentence,
  322. filename: 'test.txt',
  323. preferences: { 'Content-Type' => 'text/plain' })
  324. article.preferences.tap do |prefs|
  325. prefs['whatsapp'] = {
  326. entry_id: Faker::Number.unique.number.to_s,
  327. message_id: "wamid.#{Faker::Number.unique.number}",
  328. type: 'document',
  329. media_id: Faker::Number.unique.number.to_s
  330. }
  331. end
  332. article.save!
  333. end
  334. end
  335. trait :with_media_error do
  336. after(:create) do |article, _context|
  337. article.preferences.tap do |prefs|
  338. prefs['whatsapp'] = {
  339. entry_id: Faker::Number.unique.number.to_s,
  340. message_id: "wamid.#{Faker::Number.unique.number}",
  341. type: 'document',
  342. media_id: Faker::Number.unique.number.to_s,
  343. media_error: true,
  344. }
  345. end
  346. article.save!
  347. end
  348. end
  349. end
  350. factory :telegram_article do
  351. inbound
  352. transient do
  353. type_name { 'telegram personal-message' }
  354. channel { Channel.find(ticket.preferences[:channel_id]) }
  355. username { Faker::Internet.username }
  356. end
  357. ticket factory: %i[telegram_ticket]
  358. to { "@#{channel[:options][:bot][:username]}" }
  359. subject { nil }
  360. body { Faker::Lorem.sentence }
  361. message_id { "#{Faker::Number.unique.decimal(l_digits: 1, r_digits: 10)}@telegram" }
  362. content_type { 'text/plain' }
  363. after(:create) do |article, context|
  364. next if context.sender_name == 'Agent'
  365. context.ticket.title = article.body
  366. context.ticket.preferences.tap do |p|
  367. p['telegram'] = {
  368. bid: context.channel[:options][:bot][:id],
  369. chat_id: (article.preferences[:telegram] && article.preferences[:telegram][:chat_id]) || Faker::Number.unique.number(digits: 10),
  370. }
  371. end
  372. context.ticket.save!
  373. end
  374. trait :inbound do
  375. transient do
  376. sender_name { 'Customer' }
  377. end
  378. created_by_id { ticket.customer_id } # NB: influences the value for the from field!
  379. preferences do
  380. {
  381. message: {
  382. created_at: Time.current.to_i,
  383. message_id: message_id,
  384. from: ActionController::Parameters.new(
  385. 'id' => Faker::Number.unique.number,
  386. 'is_bot' => false,
  387. 'first_name' => Faker::Name.unique.first_name,
  388. 'last_name' => Faker::Name.unique.last_name,
  389. 'username' => username,
  390. 'language_code' => 'en',
  391. ),
  392. },
  393. update_id: Faker::Number.unique.number(digits: 8),
  394. }
  395. end
  396. end
  397. trait :outbound do
  398. transient do
  399. sender_name { 'Agent' }
  400. end
  401. to { "@#{username}" }
  402. created_by_id { create(:agent).id } # NB: influences the value for the from field!
  403. in_reply_to { "#{Faker::Number.unique.decimal(l_digits: 1, r_digits: 10)}@telegram" }
  404. preferences do
  405. {
  406. delivery_retry: 1,
  407. telegram: {
  408. date: Time.current.to_i,
  409. from_id: Faker::Number.unique.number(digits: 10),
  410. chat_id: Faker::Number.unique.number(digits: 10),
  411. message_id: Faker::Number.unique.number,
  412. },
  413. delivery_status_message: nil,
  414. delivery_status: 'success',
  415. delivery_status_date: Time.current,
  416. }
  417. end
  418. end
  419. end
  420. factory :facebook_article do
  421. inbound
  422. transient do
  423. channel { Channel.find(ticket.preferences[:channel_id]) }
  424. post_id { Faker::Number.unique.number(digits: 15) }
  425. permalink_url { "https://www.facebook.com/#{channel[:options][:pages][0][:id]}/posts/#{post_id}/?comment_id=#{post_id}" }
  426. end
  427. ticket factory: %i[facebook_ticket]
  428. subject { nil }
  429. body { Faker::Lorem.sentence }
  430. message_id { "#{Faker::Number.unique.number(digits: 16)}_#{Faker::Number.unique.number(digits: 15)}" }
  431. content_type { 'text/plain' }
  432. after(:create) do |article, context|
  433. next if context.sender_name == 'Agent'
  434. context.ticket.title = article.body
  435. context.ticket.preferences.tap do |p|
  436. p['channel_fb_object_id'] = context.post_id,
  437. p['facebook'] = {
  438. permalink_url: context.permalink_url,
  439. }
  440. end
  441. context.ticket.save!
  442. end
  443. trait :inbound do
  444. transient do
  445. type_name { 'facebook feed post' }
  446. sender_name { 'Customer' }
  447. end
  448. from { ticket.customer.fullname }
  449. to { channel[:options][:pages][0][:name] }
  450. preferences do
  451. {
  452. links: [
  453. {
  454. url: permalink_url,
  455. target: '_blank',
  456. name: 'on Facebook',
  457. },
  458. ],
  459. }
  460. end
  461. end
  462. trait :outbound do
  463. transient do
  464. type_name { 'facebook feed comment' }
  465. sender_name { 'Agent' }
  466. end
  467. from { channel[:options][:pages][0][:name] }
  468. to { ticket.customer.fullname }
  469. in_reply_to { "#{Faker::Number.unique.number(digits: 16)}_#{Faker::Number.unique.number(digits: 15)}" }
  470. preferences do
  471. {
  472. delivery_retry: 1,
  473. delivery_status_message: nil,
  474. delivery_status: 'success',
  475. delivery_status_date: Time.current,
  476. }
  477. end
  478. end
  479. end
  480. trait :with_attachment do
  481. transient do
  482. attachment { File.open('spec/fixtures/files/upload/hello_world.txt') }
  483. end
  484. after(:create) do |article, context|
  485. create(:store,
  486. object: article.class.name,
  487. o_id: article.id,
  488. data: context.attachment.read,
  489. filename: File.basename(context.attachment.path),
  490. preferences: {})
  491. end
  492. end
  493. trait :with_prepended_attachment do
  494. transient do
  495. attachment { File.open('spec/fixtures/files/upload/hello_world.txt') }
  496. override_content_type { nil }
  497. attachments_count { 1 }
  498. end
  499. after(:build) do |article, context|
  500. filename = File.basename(context.attachment.path)
  501. content_type = context.override_content_type || MIME::Types.type_for(filename).first&.content_type
  502. attachments = []
  503. context.attachments_count.times do
  504. attachments << create(:store,
  505. data: context.attachment.read,
  506. filename: filename,
  507. preferences: { 'Content-Type' => content_type })
  508. end
  509. article.attachments = attachments
  510. end
  511. end
  512. end
  513. end