legitbot 0.3.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.github/workflows/build.yml +60 -0
 - data/.gitignore +1 -0
 - data/.rubocop.yml +2 -0
 - data/.ruby-version +1 -0
 - data/Gemfile +2 -0
 - data/README.md +3 -1
 - data/Rakefile +5 -3
 - data/legitbot.gemspec +19 -18
 - data/lib/legitbot.rb +4 -0
 - data/lib/legitbot/ahrefs.rb +13 -8
 - data/lib/legitbot/apple.rb +11 -11
 - data/lib/legitbot/baidu.rb +5 -7
 - data/lib/legitbot/bing.rb +5 -7
 - data/lib/legitbot/botmatch.rb +17 -44
 - data/lib/legitbot/config/resolver.rb +18 -0
 - data/lib/legitbot/duckduckgo.rb +18 -7
 - data/lib/legitbot/facebook.rb +8 -34
 - data/lib/legitbot/google.rb +5 -8
 - data/lib/legitbot/legitbot.rb +14 -9
 - data/lib/legitbot/oracle.rb +10 -0
 - data/lib/legitbot/pinterest.rb +5 -8
 - data/lib/legitbot/twitter.rb +14 -0
 - data/lib/legitbot/validators/domains.rb +71 -0
 - data/lib/legitbot/validators/ip_ranges.rb +81 -0
 - data/lib/legitbot/version.rb +3 -1
 - data/lib/legitbot/yandex.rb +41 -12
 - data/test/ahrefs_test.rb +16 -8
 - data/test/apple_as_google_test.rb +9 -4
 - data/test/apple_test.rb +11 -4
 - data/test/botmatch_test.rb +4 -22
 - data/test/facebook_test.rb +25 -10
 - data/test/google_test.rb +24 -14
 - data/test/legitbot/validators/domains_test.rb +58 -0
 - data/test/legitbot/validators/ip_ranges_test.rb +113 -0
 - data/test/legitbot_test.rb +8 -4
 - data/test/oracle_test.rb +36 -0
 - data/test/pinterest_test.rb +26 -14
 - data/test/twitter_test.rb +36 -0
 - metadata +87 -23
 - data/.travis.yml +0 -12
 
    
        data/test/facebook_test.rb
    CHANGED
    
    | 
         @@ -1,20 +1,24 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'minitest/autorun'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'legitbot'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            module Legitbot
         
     | 
| 
       5 
7 
     | 
    
         
             
              class Facebook
         
     | 
| 
      
 8 
     | 
    
         
            +
                # rubocop:disable Layout/LineLength
         
     | 
| 
       6 
9 
     | 
    
         
             
                def self.whois
         
     | 
| 
       7 
10 
     | 
    
         
             
                  {
         
     | 
| 
       8 
     | 
    
         
            -
                    ipv4: [ 
     | 
| 
      
 11 
     | 
    
         
            +
                    ipv4: ['69.63.176.0/20', '66.220.144.0/20', '66.220.144.0/21', '69.63.184.0/21', '69.63.176.0/21', '74.119.76.0/22', '69.171.255.0/24', '173.252.64.0/18', '69.171.224.0/19', '69.171.224.0/20', '103.4.96.0/22', '69.63.176.0/24', '173.252.64.0/19', '173.252.70.0/24', '31.13.64.0/18', '31.13.24.0/21', '66.220.152.0/21', '66.220.159.0/24', '69.171.239.0/24', '69.171.240.0/20', '31.13.64.0/19', '31.13.64.0/24', '31.13.65.0/24', '31.13.67.0/24', '31.13.68.0/24', '31.13.69.0/24', '31.13.70.0/24', '31.13.71.0/24', '31.13.72.0/24', '31.13.73.0/24', '31.13.74.0/24', '31.13.75.0/24', '31.13.76.0/24', '31.13.77.0/24', '31.13.96.0/19', '31.13.66.0/24', '173.252.96.0/19', '69.63.178.0/24', '31.13.78.0/24', '31.13.79.0/24', '31.13.80.0/24', '31.13.82.0/24', '31.13.83.0/24', '31.13.84.0/24', '31.13.85.0/24', '31.13.86.0/24', '31.13.87.0/24', '31.13.88.0/24', '31.13.89.0/24', '31.13.90.0/24', '31.13.91.0/24', '31.13.92.0/24', '31.13.93.0/24', '31.13.94.0/24', '31.13.95.0/24', '69.171.253.0/24', '69.63.186.0/24', '31.13.81.0/24', '179.60.192.0/22', '179.60.192.0/24', '179.60.193.0/24', '179.60.194.0/24', '179.60.195.0/24', '185.60.216.0/22', '45.64.40.0/22', '185.60.216.0/24', '185.60.217.0/24', '185.60.218.0/24', '185.60.219.0/24', '129.134.0.0/16', '157.240.0.0/16', '157.240.8.0/24', '157.240.0.0/24', '157.240.1.0/24', '157.240.2.0/24', '157.240.3.0/24', '157.240.4.0/24', '157.240.5.0/24', '157.240.6.0/24', '157.240.7.0/24', '157.240.9.0/24', '157.240.10.0/24', '157.240.16.0/24', '157.240.19.0/24', '157.240.11.0/24', '157.240.12.0/24', '157.240.13.0/24', '157.240.14.0/24', '157.240.15.0/24', '157.240.17.0/24', '157.240.18.0/24', '157.240.20.0/24', '157.240.21.0/24', '157.240.22.0/24', '157.240.23.0/24', '157.240.0.0/17', '69.171.250.0/24', '157.240.24.0/24', '157.240.25.0/24', '199.201.64.0/24', '199.201.65.0/24', '199.201.64.0/22', '204.15.20.0/22', '157.240.192.0/24', '129.134.0.0/17', '157.240.198.0/24'],
         
     | 
| 
       9 
12 
     | 
    
         
             
                    ipv6: []
         
     | 
| 
       10 
13 
     | 
    
         
             
                  }
         
     | 
| 
       11 
14 
     | 
    
         
             
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
                # rubocop:enable Layout/LineLength
         
     | 
| 
       12 
16 
     | 
    
         
             
              end
         
     | 
| 
       13 
17 
     | 
    
         
             
            end
         
     | 
| 
       14 
18 
     | 
    
         | 
| 
       15 
19 
     | 
    
         
             
            class FacebookTest < Minitest::Test
         
     | 
| 
       16 
20 
     | 
    
         
             
              def test_valid_ip
         
     | 
| 
       17 
     | 
    
         
            -
                ip =  
     | 
| 
      
 21 
     | 
    
         
            +
                ip = '69.63.186.89'
         
     | 
| 
       18 
22 
     | 
    
         
             
                match = Legitbot::Facebook.new(ip)
         
     | 
| 
       19 
23 
     | 
    
         
             
                assert match.valid?, msg: "#{ip} is a valid Facebook IP"
         
     | 
| 
       20 
24 
     | 
    
         | 
| 
         @@ -24,25 +28,36 @@ class FacebookTest < Minitest::Test 
     | 
|
| 
       24 
28 
     | 
    
         
             
              end
         
     | 
| 
       25 
29 
     | 
    
         | 
| 
       26 
30 
     | 
    
         
             
              def test_invalid_ip
         
     | 
| 
       27 
     | 
    
         
            -
                ip =  
     | 
| 
      
 31 
     | 
    
         
            +
                ip = '127.0.0.1'
         
     | 
| 
       28 
32 
     | 
    
         
             
                match = Legitbot::Facebook.new(ip)
         
     | 
| 
       29 
33 
     | 
    
         
             
                assert match.fake?, msg: "#{ip} is a fake Facebook IP"
         
     | 
| 
       30 
34 
     | 
    
         
             
              end
         
     | 
| 
       31 
35 
     | 
    
         | 
| 
      
 36 
     | 
    
         
            +
              # rubocop:disable Metrics/AbcSize, Layout/LineLength, Metrics/MethodLength
         
     | 
| 
       32 
37 
     | 
    
         
             
              def test_user_agent
         
     | 
| 
       33 
     | 
    
         
            -
                Legitbot.bot( 
     | 
| 
      
 38 
     | 
    
         
            +
                Legitbot.bot(
         
     | 
| 
      
 39 
     | 
    
         
            +
                  'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
         
     | 
| 
      
 40 
     | 
    
         
            +
                  '31.13.76.56'
         
     | 
| 
      
 41 
     | 
    
         
            +
                ) do |bot|
         
     | 
| 
       34 
42 
     | 
    
         
             
                  assert_equal :facebook, bot.detected_as
         
     | 
| 
       35 
     | 
    
         
            -
                  assert bot.valid?, msg:  
     | 
| 
      
 43 
     | 
    
         
            +
                  assert bot.valid?, msg: 'true Facebook'
         
     | 
| 
       36 
44 
     | 
    
         
             
                end
         
     | 
| 
       37 
45 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
                Legitbot.bot( 
     | 
| 
      
 46 
     | 
    
         
            +
                Legitbot.bot(
         
     | 
| 
      
 47 
     | 
    
         
            +
                  'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
         
     | 
| 
      
 48 
     | 
    
         
            +
                  '173.252.87.8'
         
     | 
| 
      
 49 
     | 
    
         
            +
                ) do |bot|
         
     | 
| 
       39 
50 
     | 
    
         
             
                  assert_equal :facebook, bot.detected_as
         
     | 
| 
       40 
     | 
    
         
            -
                  assert bot.valid?, msg:  
     | 
| 
      
 51 
     | 
    
         
            +
                  assert bot.valid?, msg: 'true Facebook'
         
     | 
| 
       41 
52 
     | 
    
         
             
                end
         
     | 
| 
       42 
53 
     | 
    
         | 
| 
       43 
     | 
    
         
            -
                Legitbot.bot( 
     | 
| 
       44 
     | 
    
         
            -
                   
     | 
| 
       45 
     | 
    
         
            -
                   
     | 
| 
      
 54 
     | 
    
         
            +
                Legitbot.bot(
         
     | 
| 
      
 55 
     | 
    
         
            +
                  'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0',
         
     | 
| 
      
 56 
     | 
    
         
            +
                  '92.243.181.7'
         
     | 
| 
      
 57 
     | 
    
         
            +
                ) do |bot|
         
     | 
| 
      
 58 
     | 
    
         
            +
                  assert %i[facebook twitter].include?(bot.detected_as)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  assert bot.fake?, msg: 'fake Facebook'
         
     | 
| 
       46 
60 
     | 
    
         
             
                end
         
     | 
| 
       47 
61 
     | 
    
         
             
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
              # rubocop:enable Metrics/AbcSize, Layout/LineLength, Metrics/MethodLength
         
     | 
| 
       48 
63 
     | 
    
         
             
            end
         
     | 
    
        data/test/google_test.rb
    CHANGED
    
    | 
         @@ -1,42 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'minitest/autorun'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'legitbot'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            class GoogleTest < Minitest::Test
         
     | 
| 
       5 
7 
     | 
    
         
             
              def test_malicious_ip
         
     | 
| 
       6 
     | 
    
         
            -
                ip =  
     | 
| 
      
 8 
     | 
    
         
            +
                ip = '149.210.164.47'
         
     | 
| 
       7 
9 
     | 
    
         
             
                match = Legitbot::Google.new ip
         
     | 
| 
       8 
     | 
    
         
            -
                reverse_name = match.reverse_name
         
     | 
| 
       9 
     | 
    
         
            -
                assert !match.subdomain_of?("googlebot.com."), msg: "#{reverse_name} is not a subdomain of googlebot.com"
         
     | 
| 
       10 
10 
     | 
    
         
             
                assert !match.valid?, msg: "#{ip} is not a real Googlebot IP"
         
     | 
| 
       11 
11 
     | 
    
         
             
              end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
              def test_valid_ip
         
     | 
| 
       14 
     | 
    
         
            -
                ip =  
     | 
| 
      
 14 
     | 
    
         
            +
                ip = '66.249.64.141'
         
     | 
| 
       15 
15 
     | 
    
         
             
                match = Legitbot::Google.new ip
         
     | 
| 
       16 
     | 
    
         
            -
                reverse_name = match.reverse_name
         
     | 
| 
       17 
     | 
    
         
            -
                assert match.subdomain_of?("googlebot.com."), msg: "#{reverse_name} is a subdomain of googlebot.com"
         
     | 
| 
       18 
16 
     | 
    
         
             
                assert match.valid?, msg: "#{ip} is a valid Googlebot IP"
         
     | 
| 
       19 
17 
     | 
    
         
             
              end
         
     | 
| 
       20 
18 
     | 
    
         | 
| 
       21 
19 
     | 
    
         
             
              def test_malicious_ua
         
     | 
| 
       22 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
         
     | 
| 
      
 22 
     | 
    
         
            +
                  '149.210.164.47'
         
     | 
| 
      
 23 
     | 
    
         
            +
                )
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert bot, msg: 'Googlebot detected from User-Agent'
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert !bot.valid?, msg: 'Not a valid Googlebot'
         
     | 
| 
       25 
26 
     | 
    
         
             
              end
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
              def test_valid_ua
         
     | 
| 
       28 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 29 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 30 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
         
     | 
| 
      
 31 
     | 
    
         
            +
                  '66.249.64.141'
         
     | 
| 
      
 32 
     | 
    
         
            +
                )
         
     | 
| 
      
 33 
     | 
    
         
            +
                assert bot, msg: 'Googlebot detected from User-Agent'
         
     | 
| 
      
 34 
     | 
    
         
            +
                assert bot.valid?, msg: 'Valid Googlebot'
         
     | 
| 
       31 
35 
     | 
    
         
             
              end
         
     | 
| 
       32 
36 
     | 
    
         | 
| 
       33 
37 
     | 
    
         
             
              def test_valid_name
         
     | 
| 
       34 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
      
 38 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 39 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
         
     | 
| 
      
 40 
     | 
    
         
            +
                  '66.249.64.141'
         
     | 
| 
      
 41 
     | 
    
         
            +
                )
         
     | 
| 
       35 
42 
     | 
    
         
             
                assert_equal :google, bot.detected_as
         
     | 
| 
       36 
43 
     | 
    
         
             
              end
         
     | 
| 
       37 
44 
     | 
    
         | 
| 
       38 
45 
     | 
    
         
             
              def test_fake_name
         
     | 
| 
       39 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
      
 46 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 47 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
         
     | 
| 
      
 48 
     | 
    
         
            +
                  '81.1.172.108'
         
     | 
| 
      
 49 
     | 
    
         
            +
                )
         
     | 
| 
       40 
50 
     | 
    
         
             
                assert_equal :google, bot.detected_as
         
     | 
| 
       41 
51 
     | 
    
         
             
              end
         
     | 
| 
       42 
52 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'legitbot'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Legitbot
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Validators
         
     | 
| 
      
 8 
     | 
    
         
            +
                class NoDomains
         
     | 
| 
      
 9 
     | 
    
         
            +
                  include Domains
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                class DomainMatch
         
     | 
| 
      
 13 
     | 
    
         
            +
                  include Domains
         
     | 
| 
      
 14 
     | 
    
         
            +
                  domains 'search.msn.com', reverse: false
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  @resolver = Minitest::Mock.new
         
     | 
| 
      
 17 
     | 
    
         
            +
                  @resolver.expect(:getnames,
         
     | 
| 
      
 18 
     | 
    
         
            +
                                   ['po18-218.co2-6nf-srch-2b.ntwk.msn.net',
         
     | 
| 
      
 19 
     | 
    
         
            +
                                    'msnbot-157-55-39-132.search.msn.com'].map do |d|
         
     | 
| 
      
 20 
     | 
    
         
            +
                                     Resolv::DNS::Name.create(d)
         
     | 
| 
      
 21 
     | 
    
         
            +
                                   end, [String])
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @resolver.expect(:getnames,
         
     | 
| 
      
 23 
     | 
    
         
            +
                                   ['crawl-66-249-64-141.googlebot.com'].map do |d|
         
     | 
| 
      
 24 
     | 
    
         
            +
                                     Resolv::DNS::Name.create(d)
         
     | 
| 
      
 25 
     | 
    
         
            +
                                   end, [String])
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                class ReverseMatch
         
     | 
| 
      
 29 
     | 
    
         
            +
                  include Domains
         
     | 
| 
      
 30 
     | 
    
         
            +
                  domains 'search.msn.com'
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                class DomainsTest < Minitest::Test
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def test_no_domains
         
     | 
| 
      
 35 
     | 
    
         
            +
                    assert NoDomains.valid_domain?('127.0.0.1')
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def test_reverse_domain
         
     | 
| 
      
 39 
     | 
    
         
            +
                    assert DomainMatch.valid_domain?('127.0.0.1')
         
     | 
| 
      
 40 
     | 
    
         
            +
                    refute DomainMatch.valid_domain?('127.0.0.1')
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def test_reverse_ip
         
     | 
| 
      
 44 
     | 
    
         
            +
                    dlist = ['po18-218.co2-6nf-srch-2b.ntwk.msn.net',
         
     | 
| 
      
 45 
     | 
    
         
            +
                             'msnbot-157-55-39-132.search.msn.com'].map do |d|
         
     | 
| 
      
 46 
     | 
    
         
            +
                      Resolv::DNS::Name.create(d)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    ReverseMatch.resolver.stub :getnames, dlist do
         
     | 
| 
      
 50 
     | 
    
         
            +
                      ReverseMatch.resolver.stub :getaddress, '127.0.0.1' do
         
     | 
| 
      
 51 
     | 
    
         
            +
                        assert ReverseMatch.valid_domain?('127.0.0.1')
         
     | 
| 
      
 52 
     | 
    
         
            +
                        refute ReverseMatch.valid_domain?('127.0.0.2')
         
     | 
| 
      
 53 
     | 
    
         
            +
                      end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,113 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'legitbot'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Legitbot
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Validators
         
     | 
| 
      
 8 
     | 
    
         
            +
                class NoRanges
         
     | 
| 
      
 9 
     | 
    
         
            +
                  include IpRanges
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                class ArrayRanges
         
     | 
| 
      
 13 
     | 
    
         
            +
                  include IpRanges
         
     | 
| 
      
 14 
     | 
    
         
            +
                  ip_ranges '66.220.144.0/21', '2a03:2880:f234::/48'
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                class FlattenRanges
         
     | 
| 
      
 18 
     | 
    
         
            +
                  include IpRanges
         
     | 
| 
      
 19 
     | 
    
         
            +
                  ip_ranges %w[66.220.144.0/21 2a03:2880:f234::/48]
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                class EmptyRanges
         
     | 
| 
      
 23 
     | 
    
         
            +
                  include IpRanges
         
     | 
| 
      
 24 
     | 
    
         
            +
                  ip_ranges
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def initialize(ip)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @ip = ip
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                class LoadRanges
         
     | 
| 
      
 32 
     | 
    
         
            +
                  include IpRanges
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  @i = 0
         
     | 
| 
      
 35 
     | 
    
         
            +
                  ip_ranges do
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @i += 1
         
     | 
| 
      
 37 
     | 
    
         
            +
                    [@i.odd? ? '127.0.0.0/8' : '192.168.0.0/16']
         
     | 
| 
      
 38 
     | 
    
         
            +
                  end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                  def self.counter
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @i
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def initialize(ip)
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @ip = ip
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                class IpRangesTest < Minitest::Test
         
     | 
| 
      
 50 
     | 
    
         
            +
                  def test_partition_method
         
     | 
| 
      
 51 
     | 
    
         
            +
                    empty = NoRanges.partition_ips([])
         
     | 
| 
      
 52 
     | 
    
         
            +
                    assert_empty empty
         
     | 
| 
      
 53 
     | 
    
         
            +
                  end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                  def test_ipv6_partition
         
     | 
| 
      
 56 
     | 
    
         
            +
                    ipv6 = NoRanges.partition_ips(['2a03:2880:f234::/48'])
         
     | 
| 
      
 57 
     | 
    
         
            +
                    assert_nil ipv6[:ipv4].top_node
         
     | 
| 
      
 58 
     | 
    
         
            +
                    refute_nil ipv6[:ipv6].top_node
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  def test_ipv4_partition
         
     | 
| 
      
 62 
     | 
    
         
            +
                    ipv4 = NoRanges.partition_ips(['66.220.144.0/21'])
         
     | 
| 
      
 63 
     | 
    
         
            +
                    refute_nil ipv4[:ipv4].top_node
         
     | 
| 
      
 64 
     | 
    
         
            +
                    assert_nil ipv4[:ipv6].top_node
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def test_no_ranges
         
     | 
| 
      
 68 
     | 
    
         
            +
                    assert NoRanges.valid_ip?('127.0.0.1')
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def test_empty_matcher
         
     | 
| 
      
 72 
     | 
    
         
            +
                    assert_empty EmptyRanges.valid_ips
         
     | 
| 
      
 73 
     | 
    
         
            +
                    assert_empty EmptyRanges.load_ips
         
     | 
| 
      
 74 
     | 
    
         
            +
                    assert EmptyRanges.valid_ip?('127.0.0.0')
         
     | 
| 
      
 75 
     | 
    
         
            +
                    assert EmptyRanges.valid_ip?('66.220.144.1')
         
     | 
| 
      
 76 
     | 
    
         
            +
                    assert EmptyRanges.valid_ip?('2a03:2880:f234:0:0:0:0:1')
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                    matcher = EmptyRanges.new '127.0.0.0'
         
     | 
| 
      
 79 
     | 
    
         
            +
                    assert matcher.valid_ip?
         
     | 
| 
      
 80 
     | 
    
         
            +
                  end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                  def test_matcher_array
         
     | 
| 
      
 83 
     | 
    
         
            +
                    assert ArrayRanges.valid_ip?('66.220.144.1')
         
     | 
| 
      
 84 
     | 
    
         
            +
                    assert ArrayRanges.valid_ip?('2a03:2880:f234:0:0:0:0:1')
         
     | 
| 
      
 85 
     | 
    
         
            +
                    refute ArrayRanges.valid_ip?('66.220.143.1')
         
     | 
| 
      
 86 
     | 
    
         
            +
                    refute ArrayRanges.valid_ip?('2a03:2880:f233:0:0:0:0:1')
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  def test_flatten
         
     | 
| 
      
 90 
     | 
    
         
            +
                    assert FlattenRanges.valid_ip?('66.220.144.1')
         
     | 
| 
      
 91 
     | 
    
         
            +
                    assert FlattenRanges.valid_ip?('2a03:2880:f234:0:0:0:0:1')
         
     | 
| 
      
 92 
     | 
    
         
            +
                    refute FlattenRanges.valid_ip?('66.220.143.1')
         
     | 
| 
      
 93 
     | 
    
         
            +
                    refute FlattenRanges.valid_ip?('2a03:2880:f233:0:0:0:0:1')
         
     | 
| 
      
 94 
     | 
    
         
            +
                  end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  # rubocop:disable Metrics/AbcSize
         
     | 
| 
      
 97 
     | 
    
         
            +
                  def test_matcher_loader
         
     | 
| 
      
 98 
     | 
    
         
            +
                    assert_equal 0, LoadRanges.counter
         
     | 
| 
      
 99 
     | 
    
         
            +
                    assert LoadRanges.new('127.127.127.127').valid_ip?
         
     | 
| 
      
 100 
     | 
    
         
            +
                    refute LoadRanges.new('10.10.10.10').valid_ip?
         
     | 
| 
      
 101 
     | 
    
         
            +
                    refute LoadRanges.new('192.168.127.254').valid_ip?
         
     | 
| 
      
 102 
     | 
    
         
            +
                    assert_equal 1, LoadRanges.counter
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    LoadRanges.reload_ips
         
     | 
| 
      
 105 
     | 
    
         
            +
                    refute LoadRanges.new('127.127.127.127').valid_ip?
         
     | 
| 
      
 106 
     | 
    
         
            +
                    refute LoadRanges.new('10.10.10.10').valid_ip?
         
     | 
| 
      
 107 
     | 
    
         
            +
                    assert LoadRanges.new('192.168.127.254').valid_ip?
         
     | 
| 
      
 108 
     | 
    
         
            +
                    assert_equal 2, LoadRanges.counter
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # rubocop:enable Metrics/AbcSize
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
            end
         
     | 
    
        data/test/legitbot_test.rb
    CHANGED
    
    | 
         @@ -1,13 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'minitest/autorun'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'legitbot'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            class LegitbotTest < Minitest::Test
         
     | 
| 
       5 
7 
     | 
    
         
             
              def test_rules
         
     | 
| 
       6 
     | 
    
         
            -
                assert !Legitbot.bot( 
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
                assert !Legitbot.bot('Firefox', '127.0.0.1'),
         
     | 
| 
      
 9 
     | 
    
         
            +
                       msg: 'Not a bot'
         
     | 
| 
      
 10 
     | 
    
         
            +
                assert Legitbot.bot('Googlebot', '5.140.70.64'),
         
     | 
| 
      
 11 
     | 
    
         
            +
                       msg: 'No reverse resolve, bot'
         
     | 
| 
       8 
12 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                Legitbot.bot( 
     | 
| 
       10 
     | 
    
         
            -
                  flunk  
     | 
| 
      
 13 
     | 
    
         
            +
                Legitbot.bot('Firefox', '127.0.0.1') do |_bot|
         
     | 
| 
      
 14 
     | 
    
         
            +
                  flunk 'No bot Firefox is possible'
         
     | 
| 
       11 
15 
     | 
    
         
             
                end
         
     | 
| 
       12 
16 
     | 
    
         
             
              end
         
     | 
| 
       13 
17 
     | 
    
         
             
            end
         
     | 
    
        data/test/oracle_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'legitbot'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class OracleTest < Minitest::Test
         
     | 
| 
      
 7 
     | 
    
         
            +
              def test_malicious_ip
         
     | 
| 
      
 8 
     | 
    
         
            +
                ip = '149.210.164.47'
         
     | 
| 
      
 9 
     | 
    
         
            +
                match = Legitbot::Oracle.new ip
         
     | 
| 
      
 10 
     | 
    
         
            +
                assert !match.valid?, msg: "#{ip} is not a real Oracle IP"
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def test_valid_ip
         
     | 
| 
      
 14 
     | 
    
         
            +
                ip = '148.64.56.64'
         
     | 
| 
      
 15 
     | 
    
         
            +
                match = Legitbot::Oracle.new ip
         
     | 
| 
      
 16 
     | 
    
         
            +
                assert match.valid?, msg: "#{ip} is a valid Oracle IP"
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def test_malicious_ua
         
     | 
| 
      
 20 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; GrapeshotCrawler/2.0; +http://www.grapeshot.co.uk/crawler.php)',
         
     | 
| 
      
 22 
     | 
    
         
            +
                  '149.210.164.47'
         
     | 
| 
      
 23 
     | 
    
         
            +
                )
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert bot, msg: 'Oracle detected from User-Agent'
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert !bot.valid?, msg: 'Not a valid Oracle'
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def test_valid_ua
         
     | 
| 
      
 29 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 30 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; GrapeshotCrawler/2.0; +http://www.grapeshot.co.uk/crawler.php)',
         
     | 
| 
      
 31 
     | 
    
         
            +
                  '148.64.56.64'
         
     | 
| 
      
 32 
     | 
    
         
            +
                )
         
     | 
| 
      
 33 
     | 
    
         
            +
                assert bot, msg: 'Oracle detected from User-Agent'
         
     | 
| 
      
 34 
     | 
    
         
            +
                assert bot.valid?, msg: 'Valid Oracle'
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
    
        data/test/pinterest_test.rb
    CHANGED
    
    | 
         @@ -1,42 +1,54 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       1 
3 
     | 
    
         
             
            require 'minitest/autorun'
         
     | 
| 
       2 
4 
     | 
    
         
             
            require 'legitbot'
         
     | 
| 
       3 
5 
     | 
    
         | 
| 
       4 
6 
     | 
    
         
             
            class PinterestTest < Minitest::Test
         
     | 
| 
       5 
7 
     | 
    
         
             
              def test_malicious_ip
         
     | 
| 
       6 
     | 
    
         
            -
                ip =  
     | 
| 
      
 8 
     | 
    
         
            +
                ip = '149.210.164.47'
         
     | 
| 
       7 
9 
     | 
    
         
             
                match = Legitbot::Pinterest.new ip
         
     | 
| 
       8 
     | 
    
         
            -
                reverse_name = match.reverse_name
         
     | 
| 
       9 
     | 
    
         
            -
                assert !match.subdomain_of?("pinterest.com."), msg: "#{reverse_name} is not a subdomain of pinterest.com"
         
     | 
| 
       10 
10 
     | 
    
         
             
                assert !match.valid?, msg: "#{ip} is not a real Pinterest IP"
         
     | 
| 
       11 
11 
     | 
    
         
             
              end
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
              def test_valid_ip
         
     | 
| 
       14 
     | 
    
         
            -
                ip =  
     | 
| 
      
 14 
     | 
    
         
            +
                ip = '54.236.1.11'
         
     | 
| 
       15 
15 
     | 
    
         
             
                match = Legitbot::Pinterest.new ip
         
     | 
| 
       16 
     | 
    
         
            -
                reverse_name = match.reverse_name
         
     | 
| 
       17 
     | 
    
         
            -
                assert match.subdomain_of?("pinterest.com."), msg: "#{reverse_name} is a subdomain of pinterest.com"
         
     | 
| 
       18 
16 
     | 
    
         
             
                assert match.valid?, msg: "#{ip} is a valid Pinterest IP"
         
     | 
| 
       19 
17 
     | 
    
         
             
              end
         
     | 
| 
       20 
18 
     | 
    
         | 
| 
       21 
19 
     | 
    
         
             
              def test_malicious_ua
         
     | 
| 
       22 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
             
     | 
| 
      
 20 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Pinterestbot/1.0; +https://www.pinterest.com/bot.html)',
         
     | 
| 
      
 22 
     | 
    
         
            +
                  '149.210.164.47'
         
     | 
| 
      
 23 
     | 
    
         
            +
                )
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert bot, msg: 'Pinterest detected from User-Agent'
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert !bot.valid?, msg: 'Not a valid Pinterest'
         
     | 
| 
       25 
26 
     | 
    
         
             
              end
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
              def test_valid_ua
         
     | 
| 
       28 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 29 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 30 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Pinterestbot/1.0; +https://www.pinterest.com/bot.html)',
         
     | 
| 
      
 31 
     | 
    
         
            +
                  '54.236.1.11'
         
     | 
| 
      
 32 
     | 
    
         
            +
                )
         
     | 
| 
      
 33 
     | 
    
         
            +
                assert bot, msg: 'Pinterest detected from User-Agent'
         
     | 
| 
      
 34 
     | 
    
         
            +
                assert bot.valid?, msg: 'Valid Pinterest'
         
     | 
| 
       31 
35 
     | 
    
         
             
              end
         
     | 
| 
       32 
36 
     | 
    
         | 
| 
      
 37 
     | 
    
         
            +
              # rubocop:disable Metrics/LineLength
         
     | 
| 
       33 
38 
     | 
    
         
             
              def test_android_not_bot
         
     | 
| 
       34 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
      
 39 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 40 
     | 
    
         
            +
                  'Mozilla/5.0 (Linux; Android 8.0.0; SM-G965F Build/R16NW; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.64 Mobile Safari/537.36 [Pinterest/Android]',
         
     | 
| 
      
 41 
     | 
    
         
            +
                  '85.117.106.133'
         
     | 
| 
      
 42 
     | 
    
         
            +
                )
         
     | 
| 
       35 
43 
     | 
    
         
             
                assert_nil bot
         
     | 
| 
       36 
44 
     | 
    
         
             
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
              # rubocop:enable Metrics/LineLength
         
     | 
| 
       37 
46 
     | 
    
         | 
| 
       38 
47 
     | 
    
         
             
              def test_engine_name
         
     | 
| 
       39 
     | 
    
         
            -
                bot = Legitbot.bot( 
     | 
| 
      
 48 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 49 
     | 
    
         
            +
                  'Mozilla/5.0 (compatible; Pinterestbot/1.0; +https://www.pinterest.com/bot.html)',
         
     | 
| 
      
 50 
     | 
    
         
            +
                  '54.236.1.11'
         
     | 
| 
      
 51 
     | 
    
         
            +
                )
         
     | 
| 
       40 
52 
     | 
    
         
             
                assert_equal :pinterest, bot.detected_as
         
     | 
| 
       41 
53 
     | 
    
         
             
              end
         
     | 
| 
       42 
54 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'minitest/autorun'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'legitbot'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            class TwitterTest < Minitest::Test
         
     | 
| 
      
 7 
     | 
    
         
            +
              def test_malicious_ip
         
     | 
| 
      
 8 
     | 
    
         
            +
                ip = '149.210.164.47'
         
     | 
| 
      
 9 
     | 
    
         
            +
                match = Legitbot::Twitter.new ip
         
     | 
| 
      
 10 
     | 
    
         
            +
                assert !match.valid?, msg: "#{ip} is not a real Twitter IP"
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def test_valid_ip
         
     | 
| 
      
 14 
     | 
    
         
            +
                ip = '199.16.156.125'
         
     | 
| 
      
 15 
     | 
    
         
            +
                match = Legitbot::Twitter.new ip
         
     | 
| 
      
 16 
     | 
    
         
            +
                assert match.valid?, msg: "#{ip} is a valid Twitter IP"
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def test_malicious_ua
         
     | 
| 
      
 20 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 21 
     | 
    
         
            +
                  'Twitterbot/1.0',
         
     | 
| 
      
 22 
     | 
    
         
            +
                  '149.210.164.47'
         
     | 
| 
      
 23 
     | 
    
         
            +
                )
         
     | 
| 
      
 24 
     | 
    
         
            +
                assert bot, msg: 'Twitter detected from User-Agent'
         
     | 
| 
      
 25 
     | 
    
         
            +
                assert !bot.valid?, msg: 'Not a valid Twitter'
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def test_valid_ua
         
     | 
| 
      
 29 
     | 
    
         
            +
                bot = Legitbot.bot(
         
     | 
| 
      
 30 
     | 
    
         
            +
                  'Twitterbot/1.0',
         
     | 
| 
      
 31 
     | 
    
         
            +
                  '199.16.156.125'
         
     | 
| 
      
 32 
     | 
    
         
            +
                )
         
     | 
| 
      
 33 
     | 
    
         
            +
                assert bot, msg: 'Twitter detected from User-Agent'
         
     | 
| 
      
 34 
     | 
    
         
            +
                assert bot.valid?, msg: 'Valid Twitter'
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     |