Back to Repositories

Validating Plugin Version Detection from Readme Files in WPScan

This test suite validates the WPScan plugin version detection functionality through readme file analysis, ensuring accurate version extraction from various readme file formats and structures.

Test Coverage Overview

The test suite comprehensively covers plugin version detection from WordPress readme files, examining multiple scenarios and edge cases.

  • Version detection from stable tags and changelog sections
  • Handling of various version number formats (numeric, beta, release dates)
  • Edge cases including empty changelogs and non-numeric version strings
  • Integration with HTTP request handling and response parsing

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of context blocks and shared examples.

  • Stub-based HTTP request mocking
  • Dynamic fixture loading for different readme formats
  • Version object model validation
  • Confidence level verification for different detection methods

Technical Details

  • RSpec testing framework
  • WebMock for HTTP request stubbing
  • Fixture-based test data
  • Custom version model implementation
  • Aggressive detection methodology

Best Practices Demonstrated

The test suite exemplifies robust testing practices with clear separation of concerns and thorough coverage of edge cases.

  • Organized test structure with context-specific scenarios
  • Comprehensive fixture usage
  • Clear test case documentation
  • Proper setup and teardown management

wpscanteam/wpscan

spec/app/finders/plugin_version/readme_spec.rb

            
# frozen_string_literal: true

describe WPScan::Finders::PluginVersion::Readme do
  subject(:finder) { described_class.new(plugin) }
  let(:plugin)     { WPScan::Model::Plugin.new('spec', target) }
  let(:target)     { WPScan::Target.new('http://wp.lab/') }
  let(:fixtures)   { FINDERS_FIXTURES.join('plugin_version', 'readme') }

  def version(number, found_by, confidence)
    WPScan::Model::Version.new(
      number,
      found_by: format('Readme - %s (Aggressive Detection)', found_by),
      confidence: confidence,
      interesting_entries: [readme_url]
    )
  end

  def stable_tag(number)
    version(number, 'Stable Tag', 80)
  end

  def changelog_section(number)
    version(number, 'ChangeLog Section', 50)
  end

  describe '#aggressive' do
    before do
      expect(target).to receive(:content_dir).and_return('wp-content')

      allow(target).to receive(:head_or_get_params).and_return(method: :head)

      stub_request(:head, /.*/).to_return(status: 404)
      stub_request(:head, readme_url).to_return(status: 200)
    end

    let(:readme_url) { plugin.url(WPScan::Model::WpItem::READMES.sample) }

    after do
      stub_request(:get, readme_url).to_return(body: File.read(fixtures.join(@file)))

      expect(finder.aggressive).to eql @expected
    end

    context 'when no version' do
      it 'returns nil' do
        @file     = 'no_version.txt'
        @expected = nil
      end
    end

    context 'when the stable tag does not contain numbers' do
      it 'returns nil' do
        @file     = 'aa-health-calculator.txt'
        @expected = nil
      end
    end

    context 'when empty changelog section' do
      it 'returns nil' do
        @file     = 'all-in-one-facebook.txt'
        @expected = nil
      end
    end

    context 'when no changelog section' do
      it 'returns nil' do
        @file     = 'blog-reordering.txt'
        @expected = nil
      end
    end

    context 'when leaked from the stable tag' do
      it 'returns the expected versions' do
        @file     = 'simple-login-lockdown-0.4.txt'
        @expected = [stable_tag('0.4'), changelog_section('04')]
      end
    end

    context 'when leaked from the version' do
      it 'returns it' do
        @file     = 'wp-photo-plus-5.1.15.txt'
        @expected = [stable_tag('5.1.15')]
      end
    end

    context 'when version is in a release date format' do
      it 'detects and returns it' do
        @file     = 's2member.txt'
        @expected = [stable_tag('141007')]
      end
    end

    context 'when version contains letters' do
      it 'returns it' do
        @file     = 'beta1.txt'
        @expected = [stable_tag('2.0.0-beta1')]
      end
    end

    context 'when parsing the changelog for version numbers' do
      {
        'changelog_version' => '1.3',
        'wp_polls' => '2.64',
        'nextgen_gallery' => '2.0.66.33',
        'wp_user_frontend' => '1.2.3',
        'my_calendar' => '2.1.5',
        'nextgen_gallery_2' => '1.9.13',
        'advanced-most-recent-posts-mod' => '1.6.5.2',
        'a-lead-capture-contact-form-and-tab-button-by-awebvoicecom' => '3.1',
        'backup-scheduler' => '1.5.9',
        'release_date_slash' => '1.0.4',
        'cool_tag_cloud' => '2.27'
      }.each do |file, version_number|
        context "whith #{file}.txt" do
          it 'returns the expected version' do
            @file = "#{file}.txt"
            @expected = [changelog_section(version_number)]
          end
        end
      end
    end
  end
end