# frozen_string_literal: true

describe WPScan::Model::Theme do
  subject(:theme)  { described_class.new(slug, blog, opts) }
  let(:slug)       { 'spec' }
  let(:blog)       { WPScan::Target.new('http://wp.lab/') }
  let(:opts)       { {} }
  let(:fixtures)   { FIXTURES.join('models', 'theme') }

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

  describe '#new' do
    before { stub_request(:get, /.*\.css\z/).to_return(body: File.read(fixture)) }

    let(:fixture) { fixtures.join('style.css') }

    its(:url) { should eql 'http://wp.lab/wp-content/themes/spec/' }
    its(:style_url) { should eql 'http://wp.lab/wp-content/themes/spec/style.css' }

    its(:style_name)  { should eql 'Twenty Fifteen' }
    its(:style_uri)   { should eql 'https://wordpress.org/themes/twentyfifteen' }
    its(:author)      { should eql 'the WordPress team' }
    its(:author_uri)  { should eql nil }
    its(:template)    { should eql nil }
    its(:description) { should eql 'Our 2015 default theme is clean, blog-focused.' }
    its(:license)     { should eql 'GNU General Public License v2 or later' }
    its(:license_uri) { should eql 'http://www.gnu.org/licenses/gpl-2.0.html' }
    its(:tags)        { should eql 'black, blue, gray, pink, purple, white, yellow.' }
    its(:text_domain) { should eql 'twentyfifteen' }

    context 'when opts[:style_url]' do
      let(:opts) { super().merge(style_url: 'http://wp.lab/wp-content/themes/spec/custom.css') }

      its(:style_url) { should eql opts[:style_url] }
    end

    context 'when some new lines are stripped' do
      let(:fixture) { fixtures.join('stripped_new_lines.css') }

      its(:style_name)  { should eql 'Divi' }
      its(:style_uri)   { should eql 'http://www.elegantthemes.com/gallery/divi/' }
      its(:license_uri) { should eql 'http://www.gnu.org/licenses/gpl-2.0.html' }
    end

    context 'when no tags' do
      let(:fixture) { fixtures.join('no_tags.css') }

      its(:author) { should eql nil }
    end
  end

  describe '#version' do
    after do
      stub_request(:get, /.*\.css\z/)
        .to_return(body: File.read(fixtures.join('style.css')))

      expect(WPScan::Finders::ThemeVersion::Base).to receive(:find).with(theme, @expected_opts)
      theme.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 '#latest_version, #last_updated, #popular' do
    before do
      stub_request(:get, /.*\.css\z/)
      allow(theme).to receive(:db_data).and_return(db_data)
    end

    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('themes/no-vulns-popular') }

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

  describe '#outdated?' do
    before do
      stub_request(:get, /.*\.css\z/)
      allow(theme).to receive(:db_data).and_return({})
    end

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

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

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

      context 'when version' do
        before do
          expect(theme)
            .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(theme).to receive(:version).at_least(1).and_return(nil) }

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

      context 'when version' do
        before do
          expect(theme)
            .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 do
      stub_request(:get, /.*\.css\z/)
      allow(theme).to receive(:db_data).and_return(db_data)
    end

    after do
      expect(theme.vulnerabilities).to eq @expected
      expect(theme.vulnerable?).to eql !@expected.empty?
    end

    context 'when theme 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('themes/no-vulns-popular') }

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

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

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

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

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

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

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

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

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

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

  describe '#parent_theme' do
    before do
      stub_request(:get, blog.url('wp-content/themes/spec/style.css'))
        .to_return(body: File.read(fixtures.join(main_theme)))
    end

    context 'when no template' do
      let(:main_theme) { 'style.css' }

      it 'returns nil' do
        expect(theme.parent_theme).to eql nil
      end
    end

    context 'when a template' do
      let(:main_theme) { 'child_style.css' }
      let(:parent_url) { blog.url('wp-content/themes/twentyfourteen/custom.css') }

      before do
        stub_request(:get, parent_url)
          .to_return(body: File.read(fixtures.join('style.css')))
      end

      %w[child_style windows_line_endings].each do |fixture|
        context "when #{fixture}" do
          let(:main_theme) { "#{fixture}.css" }

          it 'returns the expected theme' do
            parent = theme.parent_theme

            expect(parent).to eql described_class.new(
              'twentyfourteen', blog,
              style_url: parent_url,
              confidence: 100,
              found_by: 'Parent Themes (Passive Detection)'
            )
            expect(parent.style_url).to eql parent_url
          end
        end
      end
    end
  end

  describe '#parent_themes' do
    xit
  end

  describe '#==' do
    before { stub_request(:get, /.*\.css\z/) }

    context 'when default style' do
      it 'returns true when equal' do
        expect(theme == described_class.new(slug, blog, opts)).to be true
      end

      it 'returns false when not equal' do
        expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec.css'))).to be false
      end
    end

    context 'when custom style' do
      let(:opts) { super().merge(style_url: 'spec.css') }

      it 'returns true when equal' do
        expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec.css'))).to be true
      end

      it 'returns false when not equal' do
        expect(theme == described_class.new(slug, blog, opts.merge(style_url: 'spec2.css'))).to be false
      end
    end
  end
end
