spam_check_spec.rb 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. # frozen_string_literal: true
  2. require 'rails_helper'
  3. RSpec.describe SpamCheck do
  4. let!(:sender) { Fabricate(:account) }
  5. let!(:alice) { Fabricate(:account, username: 'alice') }
  6. let!(:bob) { Fabricate(:account, username: 'bob') }
  7. def status_with_html(text, options = {})
  8. status = PostStatusService.new.call(sender, { text: text }.merge(options))
  9. status.update_columns(text: Formatter.instance.format(status), local: false)
  10. status
  11. end
  12. describe '#hashable_text' do
  13. it 'removes mentions from HTML for remote statuses' do
  14. status = status_with_html('@alice Hello')
  15. expect(described_class.new(status).hashable_text).to eq 'hello'
  16. end
  17. it 'removes mentions from text for local statuses' do
  18. status = PostStatusService.new.call(alice, text: "Hey @#{sender.username}, how are you?")
  19. expect(described_class.new(status).hashable_text).to eq 'hey , how are you?'
  20. end
  21. end
  22. describe '#insufficient_data?' do
  23. it 'returns true when there is no text' do
  24. status = status_with_html('@alice')
  25. expect(described_class.new(status).insufficient_data?).to be true
  26. end
  27. it 'returns false when there is text' do
  28. status = status_with_html('@alice h')
  29. expect(described_class.new(status).insufficient_data?).to be false
  30. end
  31. end
  32. describe '#digest' do
  33. it 'returns a string' do
  34. status = status_with_html('@alice Hello world')
  35. expect(described_class.new(status).digest).to be_a String
  36. end
  37. end
  38. describe '#spam?' do
  39. it 'returns false for a unique status' do
  40. status = status_with_html('@alice Hello')
  41. expect(described_class.new(status).spam?).to be false
  42. end
  43. it 'returns false for different statuses to the same recipient' do
  44. status1 = status_with_html('@alice Hello')
  45. described_class.new(status1).remember!
  46. status2 = status_with_html('@alice Are you available to talk?')
  47. expect(described_class.new(status2).spam?).to be false
  48. end
  49. it 'returns false for statuses with different content warnings' do
  50. status1 = status_with_html('@alice Are you available to talk?')
  51. described_class.new(status1).remember!
  52. status2 = status_with_html('@alice Are you available to talk?', spoiler_text: 'This is a completely different matter than what I was talking about previously, I swear!')
  53. expect(described_class.new(status2).spam?).to be false
  54. end
  55. it 'returns false for different statuses to different recipients' do
  56. status1 = status_with_html('@alice How is it going?')
  57. described_class.new(status1).remember!
  58. status2 = status_with_html('@bob Are you okay?')
  59. expect(described_class.new(status2).spam?).to be false
  60. end
  61. it 'returns false for very short different statuses to different recipients' do
  62. status1 = status_with_html('@alice 🙄')
  63. described_class.new(status1).remember!
  64. status2 = status_with_html('@bob Huh?')
  65. expect(described_class.new(status2).spam?).to be false
  66. end
  67. it 'returns false for statuses with no text' do
  68. status1 = status_with_html('@alice')
  69. described_class.new(status1).remember!
  70. status2 = status_with_html('@bob')
  71. expect(described_class.new(status2).spam?).to be false
  72. end
  73. it 'returns true for duplicate statuses to the same recipient' do
  74. described_class::THRESHOLD.times do
  75. status1 = status_with_html('@alice Hello')
  76. described_class.new(status1).remember!
  77. end
  78. status2 = status_with_html('@alice Hello')
  79. expect(described_class.new(status2).spam?).to be true
  80. end
  81. it 'returns true for duplicate statuses to different recipients' do
  82. described_class::THRESHOLD.times do
  83. status1 = status_with_html('@alice Hello')
  84. described_class.new(status1).remember!
  85. end
  86. status2 = status_with_html('@bob Hello')
  87. expect(described_class.new(status2).spam?).to be true
  88. end
  89. it 'returns true for nearly identical statuses with random numbers' do
  90. source_text = 'Sodium, atomic number 11, was first isolated by Humphry Davy in 1807. A chemical component of salt, he named it Na in honor of the saltiest region on earth, North America.'
  91. described_class::THRESHOLD.times do
  92. status1 = status_with_html('@alice ' + source_text + ' 1234')
  93. described_class.new(status1).remember!
  94. end
  95. status2 = status_with_html('@bob ' + source_text + ' 9568')
  96. expect(described_class.new(status2).spam?).to be true
  97. end
  98. end
  99. describe '#skip?' do
  100. it 'returns true when the sender is already silenced' do
  101. status = status_with_html('@alice Hello')
  102. sender.silence!
  103. expect(described_class.new(status).skip?).to be true
  104. end
  105. it 'returns true when the mentioned person follows the sender' do
  106. status = status_with_html('@alice Hello')
  107. alice.follow!(sender)
  108. expect(described_class.new(status).skip?).to be true
  109. end
  110. it 'returns false when even one mentioned person doesn\'t follow the sender' do
  111. status = status_with_html('@alice @bob Hello')
  112. alice.follow!(sender)
  113. expect(described_class.new(status).skip?).to be false
  114. end
  115. it 'returns true when the sender is replying to a status that mentions the sender' do
  116. parent = PostStatusService.new.call(alice, text: "Hey @#{sender.username}, how are you?")
  117. status = status_with_html('@alice @bob Hello', thread: parent)
  118. expect(described_class.new(status).skip?).to be true
  119. end
  120. end
  121. describe '#remember!' do
  122. let(:status) { status_with_html('@alice') }
  123. let(:spam_check) { described_class.new(status) }
  124. let(:redis_key) { spam_check.send(:redis_key) }
  125. it 'remembers' do
  126. expect(Redis.current.exists(redis_key)).to be true
  127. spam_check.remember!
  128. expect(Redis.current.exists(redis_key)).to be true
  129. end
  130. end
  131. describe '#reset!' do
  132. let(:status) { status_with_html('@alice') }
  133. let(:spam_check) { described_class.new(status) }
  134. let(:redis_key) { spam_check.send(:redis_key) }
  135. before do
  136. spam_check.remember!
  137. end
  138. it 'resets' do
  139. expect(Redis.current.exists(redis_key)).to be true
  140. spam_check.reset!
  141. expect(Redis.current.exists(redis_key)).to be false
  142. end
  143. end
  144. describe '#flag!' do
  145. let!(:status1) { status_with_html('@alice General Kenobi you are a bold one') }
  146. let!(:status2) { status_with_html('@alice @bob General Kenobi, you are a bold one') }
  147. before do
  148. described_class.new(status1).remember!
  149. described_class.new(status2).flag!
  150. end
  151. it 'creates a report about the account' do
  152. expect(sender.targeted_reports.unresolved.count).to eq 1
  153. end
  154. it 'attaches both matching statuses to the report' do
  155. expect(sender.targeted_reports.first.status_ids).to include(status1.id, status2.id)
  156. end
  157. end
  158. end