Back to Repositories

Validating WPScan Plugin Model Functionality in wpscanteam/wpscan

This test suite comprehensively validates the WPScan Plugin model functionality, focusing on version detection, vulnerability assessment, and metadata handling. The tests ensure proper plugin initialization, version management, and security vulnerability detection across different scenarios.

Test Coverage Overview

The test suite provides extensive coverage of the WPScan Plugin model’s core functionality.

Key areas tested include:
  • Plugin initialization and URL construction
  • Version detection and management
  • Readme file handling
  • Version comparison and outdated status checking
  • Vulnerability detection and assessment

Implementation Analysis

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

Notable implementation features:
  • Mock object usage for HTTP requests
  • Comprehensive version comparison logic
  • Database interaction simulation
  • Complex vulnerability detection scenarios

Technical Details

Testing tools and configuration:
  • RSpec test framework
  • Mocking with expect().to receive()
  • Shared context patterns
  • Before/After hooks for setup/teardown
  • Custom matchers for version comparison

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of concerns, comprehensive edge case coverage, and proper mocking patterns.

Notable practices:
  • Isolated test contexts
  • Explicit version handling
  • Comprehensive vulnerability scenarios
  • Clear test organization

wpscanteam/wpscan

spec/app/models/plugin_spec.rb

            
# frozen_string_literal: true

describe WPScan::Model::Plugin do
  subject(:plugin) { described_class.new(slug, blog, opts) }
  let(:slug)       { 'spec' }
  let(:blog)       { WPScan::Target.new('http://wp.lab/') }
  let(:opts)       { {} }

  before { expect(blog).to receive(:content_dir).and_return('wp-content') }

  describe '#new' do
    its(:url) { should eql 'http://wp.lab/wp-content/plugins/spec/' }
  end

  describe '#version' do
    after do
      expect(WPScan::Finders::PluginVersion::Base).to receive(:find).with(plugin, @expected_opts)

      plugin.version(version_opts)
    end

    let(:default_opts) { {} }

    context 'when no :detection_mode' do
      context 'when no :mode opt supplied' do
        let(:version_opts) { { something: 'k' } }

        it 'calls the finder with the correct parameters' do
          @expected_opts = version_opts
        end
      end

      context 'when :mode supplied' do
        let(:version_opts) { { mode: :passive } }

        it 'calls the finder with the correct parameters' do
          @expected_opts = default_opts.merge(mode: :passive)
        end
      end
    end

    context 'when :detection_mode' do
      let(:opts) { super().merge(mode: :passive) }

      context 'when no :mode' do
        let(:version_opts) { {} }

        it 'calls the finder without mode' do
          @expected_opts = version_opts
        end
      end

      context 'when :mode' do
        let(:version_opts) { { mode: :mixed } }

        it 'calls the finder with the :mixed mode' do
          @expected_opts = default_opts.merge(mode: :mixed)
        end
      end
    end
  end

  describe 'potential_readme_filenames' do
    context 'when not set in the DF file' do
      its(:potential_readme_filenames) { should eql described_class::READMES }
    end

    context 'when set in the DF file' do
      context 'as a string' do
        let(:slug) { 'photoblocks-grid-gallery' }

        its(:potential_readme_filenames) { should eql %w[README.txt] }
      end

      context 'as an array' do
        let(:slug) { 'customerlabs-actionrecorder' }

        its(:potential_readme_filenames) { should eql %w[Readme.txt Readme.md] }
      end
    end
  end

  describe '#latest_version, #last_updated, #popular' do
    before { allow(plugin).to receive(:db_data).and_return(db_data) }

    context 'when no db_data and no metadata' do
      let(:slug)    { 'not-known' }
      let(:db_data) { {} }

      its(:latest_version) { should be_nil }
      its(:last_updated) { should be_nil }
      its(:popular?) { should be false }
    end

    context 'when no db_data but metadata' do
      let(:slug) { 'no-vulns-popular' }
      let(:db_data) { {} }

      its(:latest_version) { should eql WPScan::Model::Version.new('2.0') }
      its(:last_updated) { should eql '2015-05-16T00:00:00.000Z' }
      its(:popular?) { should be true }
    end

    context 'when db_data' do
      let(:slug) { 'no-vulns-popular' }
      let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }

      its(:latest_version) { should eql WPScan::Model::Version.new('2.1') }
      its(:last_updated) { should eql '2015-05-16T00:00:00.000Z-via-api' }
      its(:popular?) { should be true }
    end
  end

  describe '#outdated?' do
    before { allow(plugin).to receive(:db_data).and_return({}) }

    context 'when last_version' do
      let(:slug) { 'no-vulns-popular' }

      context 'when no version' do
        before { expect(plugin).to receive(:version).at_least(1).and_return(nil) }

        its(:outdated?) { should eql false }
      end

      context 'when version' do
        before do
          expect(plugin)
            .to receive(:version)
            .at_least(1)
            .and_return(WPScan::Model::Version.new(version_number))
        end

        context 'when version < latest_version' do
          let(:version_number) { '1.2' }

          its(:outdated?) { should eql true }
        end

        context 'when version >= latest_version' do
          let(:version_number) { '3.0' }

          its(:outdated?) { should eql false }
        end
      end
    end

    context 'when no latest_version' do
      let(:slug) { 'vulnerable-not-popular' }

      context 'when no version' do
        before { expect(plugin).to receive(:version).at_least(1).and_return(nil) }

        its(:outdated?) { should eql false }
      end

      context 'when version' do
        before do
          expect(plugin)
            .to receive(:version)
            .at_least(1)
            .and_return(WPScan::Model::Version.new('1.0'))
        end

        its(:outdated?) { should eql false }
      end
    end
  end

  describe '#vulnerabilities' do
    before { allow(plugin).to receive(:db_data).and_return(db_data) }

    after do
      expect(plugin.vulnerabilities).to eq @expected
      expect(plugin.vulnerable?).to eql [email protected]?
    end

    context 'when plugin not in the DB' do
      let(:slug)    { 'not-in-db' }
      let(:db_data) { {} }

      it 'returns an empty array' do
        @expected = []
      end
    end

    context 'when in the DB' do
      context 'when no vulnerabilities' do
        let(:slug)    { 'no-vulns-popular' }
        let(:db_data) { vuln_api_data_for('plugins/no-vulns-popular') }

        it 'returns an empty array' do
          @expected = []
        end
      end

      context 'when vulnerabilities' do
        context 'when only fixed_in' do
          let(:slug)    { 'vulnerable-not-popular' }
          let(:db_data) { vuln_api_data_for('plugins/vulnerable-not-popular') }

          let(:all_vulns) do
            [
              WPScan::Vulnerability.new(
                'First Vuln <= 6.3.10 - LFI',
                references: { wpvulndb: '1' },
                type: 'LFI',
                fixed_in: '6.3.10'
              ),
              WPScan::Vulnerability.new('No Fixed In', references: { wpvulndb: '2' })
            ]
          end

          context 'when no plugin version' do
            before { expect(plugin).to receive(:version).at_least(1).and_return(false) }

            it 'returns all the vulnerabilities' do
              @expected = all_vulns
            end
          end

          context 'when plugin version' do
            before do
              expect(plugin)
                .to receive(:version)
                .at_least(1)
                .and_return(WPScan::Model::Version.new(number))
            end

            context 'when < to fixed_in' do
              let(:number) { '5.0' }

              it 'returns it' do
                @expected = all_vulns
              end
            end

            context 'when >= to fixed_in' do
              let(:number) { '6.3.10' }

              it 'does not return it ' do
                @expected = [all_vulns.last]
              end
            end
          end
        end

        context 'when introduced_in' do
          let(:db_data) { vuln_api_data_for('plugins/vulnerable-introduced-in') }

          let(:all_vulns) do
            [
              WPScan::Vulnerability.new(
                'Introduced In 6.4',
                fixed_in: '6.5',
                introduced_in: '6.4',
                references: { wpvulndb: '1' }
              )
            ]
          end

          context 'when no plugin version' do
            before { expect(plugin).to receive(:version).at_least(1).and_return(false) }

            it 'returns all the vulnerabilities' do
              @expected = all_vulns
            end
          end

          context 'when plugin version' do
            before do
              expect(plugin)
                .to receive(:version)
                .at_least(1)
                .and_return(WPScan::Model::Version.new(number))
            end

            context 'when < to introduced_in' do
              let(:number) { '5.0' }

              it 'does not return it' do
                @expected = []
              end
            end

            context 'when >= to fixed_in' do
              let(:number) { '6.5' }

              it 'does not return it' do
                @expected = []
              end
            end

            context 'when >= to introduced_in' do
              let(:number) { '6.4' }

              it 'returns it' do
                @expected = all_vulns
              end
            end
          end
        end
      end
    end
  end
end