account_activity_api_spec.rb 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe 'Twitter > Account Activity API', :use_vcr, integration: true, required_envs: %w[TWITTER_CONSUMER_KEY TWITTER_CONSUMER_SECRET TWITTER_OAUTH_TOKEN TWITTER_OAUTH_TOKEN_SECRET TWITTER_USER_ID TWITTER_DM_REAL_RECIPIENT TWITTER_SEARCH_CONSUMER_KEY TWITTER_SEARCH_CONSUMER_SECRET TWITTER_SEARCH_OAUTH_TOKEN TWITTER_SEARCH_OAUTH_TOKEN_SECRET TWITTER_SEARCH_USER_ID] do # rubocop:disable RSpec/DescribeClass
  4. subject(:channel) { create(:twitter_channel, custom_options: { sync: { search: nil } }) }
  5. let(:twitter_helper) do
  6. RSpecTwitter::Helper.new(auth_data_search_app)
  7. end
  8. let(:twitter_helper_channel) do
  9. RSpecTwitter::Helper.new(auth_data_channel_app)
  10. end
  11. let(:channel_attributes) do
  12. {
  13. 'status_in' => 'ok',
  14. 'last_log_in' => '',
  15. 'status_out' => nil,
  16. 'last_log_out' => nil,
  17. }
  18. end
  19. def auth_data_channel_app
  20. {
  21. consumer_key: ENV['TWITTER_CONSUMER_KEY'],
  22. consumer_secret: ENV['TWITTER_CONSUMER_SECRET'],
  23. oauth_token: ENV['TWITTER_OAUTH_TOKEN'],
  24. oauth_token_secret: ENV['TWITTER_OAUTH_TOKEN_SECRET'],
  25. }
  26. end
  27. def auth_data_search_app
  28. {
  29. consumer_key: ENV['TWITTER_SEARCH_CONSUMER_KEY'],
  30. consumer_secret: ENV['TWITTER_SEARCH_CONSUMER_SECRET'],
  31. oauth_token: ENV['TWITTER_SEARCH_OAUTH_TOKEN'],
  32. oauth_token_secret: ENV['TWITTER_SEARCH_OAUTH_TOKEN_SECRET'],
  33. }
  34. end
  35. before :all do # rubocop:disable RSpec/BeforeAfterAll
  36. if %w[1 true].include?(ENV['CI_IGNORE_CASSETTES'])
  37. RSpecTwitter::Helper.new(auth_data_search_app).delete_old_tweets
  38. RSpecTwitter::Helper.new(auth_data_channel_app).delete_old_tweets
  39. end
  40. end
  41. it 'sets successful status attributes' do
  42. expect { channel.fetch }
  43. .to change { channel.reload.attributes }
  44. .to hash_including(channel_attributes)
  45. end
  46. context 'with search term configured' do
  47. subject(:channel) { create(:twitter_channel, custom_options: { sync: { search: [ { term: identifier, group_id: Group.first.id } ] } }) }
  48. let(:identifier) do
  49. random_number = %w[1 true].include?(ENV['CI_IGNORE_CASSETTES']) ? SecureRandom.uuid.delete('-') : '0509d41afd66476fa52a1c3892f669eb'
  50. "zammad_testing_#{random_number}"
  51. end
  52. let(:ticket_title) { "Come and join our team to bring Zammad even further forward! #{identifier}" }
  53. after do
  54. twitter_helper.delete_all_tweets(identifier)
  55. twitter_helper_channel.delete_all_tweets(identifier)
  56. end
  57. context 'with recent tweets' do
  58. before do
  59. twitter_helper.create_tweet(ticket_title)
  60. twitter_helper.create_tweet("dummy API activity test! #{identifier}")
  61. twitter_helper_channel.ensure_tweet_availability(identifier, 2)
  62. end
  63. let(:expected_ticket_attributes) do
  64. {
  65. 'title' => ticket_title.size > 80 ? "#{ticket_title[0..79]}..." : ticket_title,
  66. 'preferences' => {
  67. 'channel_id' => channel.id,
  68. 'channel_screen_name' => channel.options[:user][:screen_name]
  69. },
  70. }
  71. end
  72. it 'creates an article for each recent tweet', :aggregate_failures do
  73. expect { channel.fetch }.to change(Ticket, :count).by(2)
  74. expect(Ticket.last.attributes).to include(expected_ticket_attributes)
  75. end
  76. end
  77. context 'with responses to other tweets' do
  78. before do
  79. parent_tweet = twitter_helper.create_tweet('Parent tweet without identifier')
  80. twitter_helper.create_tweet("Response test! #{identifier}", in_reply_to_status_id: parent_tweet.id)
  81. twitter_helper_channel.ensure_tweet_availability(identifier, 1)
  82. end
  83. let(:ticket_articles) { Ticket.last.articles }
  84. it 'creates articles for parent tweets as well', :aggregate_failures do
  85. expect { channel.fetch }.to change(Ticket, :count).by(1)
  86. expect(ticket_articles.first.body).not_to include(identifier) # parent tweet
  87. expect(ticket_articles.last.body).to include(identifier) # search result
  88. end
  89. end
  90. context 'with "track_retweets" option' do
  91. before do
  92. tweet = twitter_helper_channel.create_tweet("Zammad is amazing! #{identifier}")
  93. twitter_helper.create_retweet(tweet.id)
  94. twitter_helper_channel.ensure_tweet_availability(identifier, 2)
  95. end
  96. context 'when set to false' do
  97. it 'skips retweets' do
  98. expect { channel.fetch }
  99. .not_to change { Ticket.where('title LIKE ?', 'RT @%').count }.from(0)
  100. end
  101. end
  102. context 'when set to true' do
  103. subject(:channel) { create(:twitter_channel, custom_options: { sync: { track_retweets: true, search: [ { term: identifier, group_id: Group.first.id } ] } }) }
  104. it 'creates an article for each recent tweet/retweet' do
  105. expect { channel.fetch }
  106. .to change { Ticket.where('title LIKE ?', 'RT @%').count }.by(1)
  107. .and change(Ticket, :count).by(1)
  108. end
  109. end
  110. end
  111. context 'with "import_older_tweets" option' do
  112. before do
  113. twitter_helper.create_tweet("Zammad is amazing! #{identifier}")
  114. twitter_helper.create_tweet("Such. A. Beautiful. Helpdesk. Tool. #{identifier}")
  115. twitter_helper.create_tweet("Need a helpdesk tool? Zammad <3 #{identifier}")
  116. twitter_helper_channel.ensure_tweet_availability(identifier, 3)
  117. travel 16.days
  118. channel.update!(created_at: Time.zone.now.utc)
  119. travel_back
  120. end
  121. context 'when false (default)' do
  122. it 'skips tweets 15+ days older than channel itself' do
  123. expect { channel.fetch }.not_to change(Ticket, :count)
  124. end
  125. end
  126. context 'when true' do
  127. subject(:channel) { create(:twitter_channel, custom_options: { sync: { import_older_tweets: true, search: [ { term: identifier, group_id: Group.first.id } ] } }) }
  128. it 'creates an article for each tweet' do
  129. expect { channel.fetch }.to change(Ticket, :count).by(3)
  130. end
  131. end
  132. end
  133. context 'when fetched tweets have already been imported' do
  134. before do
  135. tweet_ids = []
  136. 3.times do |index|
  137. tweet = twitter_helper.create_tweet("Tweet #{index}! #{identifier}")
  138. tweet_ids << tweet.id
  139. end
  140. twitter_helper_channel.ensure_tweet_availability(identifier, 3)
  141. tweet_ids.each { |tweet_id| create(:ticket_article, message_id: tweet_id) }
  142. end
  143. it 'does not import duplicates' do
  144. expect { channel.fetch }.not_to change(Ticket::Article, :count)
  145. end
  146. end
  147. context 'with a very common search term' do
  148. subject(:channel) { create(:twitter_channel, custom_options: { sync: { search: [ { term: 'corona', group_id: Group.first.id } ] } }) }
  149. let(:twitter_articles) { Ticket::Article.joins(:type).where(ticket_article_types: { name: 'twitter status' }) }
  150. before do
  151. stub_const('TwitterSync::MAX_TWEETS_PER_IMPORT', 10)
  152. end
  153. # Note that this rate limiting is partially duplicated
  154. # in #fetchable?, which prevents #fetch from running
  155. # more than once in a 20-minute period.
  156. it 'imports max. ~120 articles every 15 minutes', :aggregate_failures do
  157. freeze_time
  158. channel.fetch
  159. expect((twitter_articles - Ticket.last.articles).count).to be <= 10
  160. expect(twitter_articles.count).to be > 10
  161. travel(10.minutes)
  162. expect { channel.fetch }.not_to change(Ticket::Article, :count)
  163. travel(6.minutes)
  164. expect { channel.fetch }.to change(Ticket::Article, :count)
  165. travel_back
  166. end
  167. end
  168. end
  169. end