# frozen_string_literal: true

describe WPScan::Controller::Core do
  subject(:core)       { described_class.new }
  let(:target_url)     { 'http://ex.lo/' }
  let(:cli_args)       { "--url #{target_url}" }

  before do
    described_class.reset
    WPScan::ParsedCli.options = rspec_parsed_options(cli_args)
  end

  describe '#cli_options' do
    its(:cli_options) { should_not be_empty }
    its(:cli_options) { should be_a Array }

    it 'contains to correct options' do
      cli_options = core.cli_options
      expect(cli_options.map(&:to_sym)).to include(:url, :server, :force, :update)

      # Ensures the :url is the first one and is correctly setup
      expect(cli_options.first.to_sym).to eql :url
      expect(cli_options.first.required_unless).to match_array %i[update help hh version]
    end
  end

  describe '#load_server_module' do
    after do
      expect(core.target).to receive(:server).and_return(@stubbed_server)
      expect(core.load_server_module).to eql @expected

      [core.target, WPScan::Model::WpItem.new(target_url, core.target)].each do |instance|
        expect(instance).to respond_to(:directory_listing?)
        expect(instance).to respond_to(:directory_listing_entries)

        # The below doesn't work, the module would have to be removed from the class
        # TODO: find a way to test this
        # expect(instance.server).to eql @expected if instance.is_a? WPScan::Model::WpItem
      end
    end

    context 'when no --server supplied' do
      %i[Apache IIS Nginx].each do |server|
        it "loads the #{server} module and returns :#{server}" do
          @stubbed_server = server
          @expected       = server
        end
      end
    end

    context 'when --server' do
      %i[apache iis nginx].each do |server|
        context "when #{server}" do
          let(:cli_args) { "#{super()} --server #{server}" }
          let(:servers)  { [:Apache, nil, :IIS, :Nginx] }

          it "loads the #{server.capitalize} module and returns :#{server}" do
            @stubbed_server = servers.sample
            @expected       = server == :iis ? :IIS : server.to_s.camelize.to_sym
          end
        end
      end
    end
  end

  describe '#update_db_required?' do
    context 'when missing files' do
      before { expect(core.local_db).to receive(:missing_files?).ordered.and_return(true) }

      context 'when --no-update' do
        let(:cli_args) { "#{super()} --no-update" }

        it 'raises an error' do
          expect { core.update_db_required? }.to raise_error(WPScan::Error::MissingDatabaseFile)
        end
      end

      context 'otherwise' do
        its(:update_db_required?) { should eql true }
      end
    end

    context 'when not missing files' do
      before { expect(core.local_db).to receive(:missing_files?).ordered.and_return(false) }

      context 'when --update' do
        let(:cli_args) { "#{super()} --update" }

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

      context 'when --no-update' do
        let(:cli_args) { "#{super()} --no-update" }

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

      context 'when user_interation (i.e cli output)' do
        let(:cli_args) { "#{super()} --format cli" }

        context 'when the db is up-to-date' do
          before { expect(core.local_db).to receive(:outdated?).and_return(false) }

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

        context 'when the db is outdated' do
          before do
            allow(core).to receive(:user_interaction?).and_return(true)
            expect(core.local_db).to receive(:outdated?).ordered.and_return(true)
            expect(core.formatter).to receive(:output).with('@notice', hash_including(:msg), 'core').ordered
            expect($stdout).to receive(:write).ordered # for the print()
          end

          context 'when a positive answer' do
            before do
              allow($stdin).to receive(:gets).and_return("Yes\n")
            end

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

          context 'when a negative answer' do
            before do
              allow($stdin).to receive(:gets).and_return("No\n")
            end

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

      context 'when no user_interation' do
        let(:cli_args) { "#{super()} --format json" }

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

  describe '#before_scan' do
    before do
      stub_request(:get, target_url)

      expect(core.formatter).to receive(:output).with('banner', hash_including(verbose: nil), 'core')

      expect(core).to receive(:update_db_required?).and_return(false) unless WPScan::ParsedCli.update
    end

    context 'when --update' do
      before do
        expect(core.formatter).to receive(:output)
          .with('db_update_started', hash_including(verbose: nil), 'core').ordered

        expect_any_instance_of(WPScan::DB::Updater).to receive(:update)

        expect(core.formatter).to receive(:output)
          .with('db_update_finished', hash_including(verbose: nil), 'core').ordered
      end

      context 'when the --url is not supplied' do
        let(:cli_args) { '--update' }

        it 'calls the formatter when started and finished to update the db and exit' do
          expect { core.before_scan }.to raise_error(SystemExit)
        end
      end

      context 'when --url is supplied' do
        let(:cli_args) { "#{super()} --update" }

        before do
          expect(core).to receive(:load_server_module)
          expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
          expect(core.target).to receive(:wordpress_hosted?).and_return(false)
        end

        it 'calls the formatter when started and finished to update the db' do
          expect { core.before_scan }.to_not raise_error
        end
      end
    end

    context 'when hosted on wordpress.com' do
      let(:target_url) { 'http://ex.wordpress.com' }

      before { expect(core).to receive(:load_server_module) }

      it 'raises an error' do
        expect { core.before_scan }.to raise_error(WPScan::Error::WordPressHosted)
      end
    end

    context 'when not hosted on wordpress.com' do
      before { allow(core.target).to receive(:wordpress_hosted?).and_return(false) }

      context 'when a redirect occurs' do
        before do
          stub_request(:any, target_url)

          expect(core.target).to receive(:homepage_res)
            .at_least(1)
            .and_return(Typhoeus::Response.new(effective_url: redirection, body: ''))
        end

        context 'to the wp-admin/install.php' do
          let(:redirection) { "#{target_url}wp-admin/install.php" }

          it 'calls the formatter with the correct parameters and exit' do
            expect(core.formatter).to receive(:output)
              .with('not_fully_configured', hash_including(url: redirection), 'core').ordered

            # TODO: Would be cool to be able to test the exit code
            expect { core.before_scan }.to raise_error(SystemExit)
          end
        end

        context 'to something else' do
          let(:redirection) { 'http://g.com/' }

          it 'raises an error' do
            expect { core.before_scan }.to raise_error(CMSScanner::Error::HTTPRedirect)
          end
        end

        context 'to another path with the wp-admin/install.php in the query' do
          let(:redirection) { "#{target_url}index.php?a=/wp-admin/install.php" }

          context 'when wordpress' do
            it 'does not raise an error' do
              expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)

              expect { core.before_scan }.to_not raise_error
            end
          end

          context 'when not wordpress' do
            it 'raises an error' do
              expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)

              expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
            end
          end
        end
      end

      context 'when wordpress' do
        before do
          expect(core).to receive(:load_server_module)
          expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true)
        end

        it 'does not raise any error' do
          expect { core.before_scan }.to_not raise_error
        end
      end

      context 'when not wordpress' do
        before do
          expect(core).to receive(:load_server_module)
        end

        context 'when no --force' do
          before { expect(core.target).to receive(:maybe_add_cookies) }

          context 'when no cookies added or still not wordpress after being added' do
            it 'raises an error' do
              expect(core.target).to receive(:wordpress?).twice.with(:mixed).and_return(false)

              expect { core.before_scan }.to raise_error(WPScan::Error::NotWordPress)
            end
          end

          context 'when the added cookies solved it' do
            it 'does not raise an error' do
              expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false).ordered
              expect(core.target).to receive(:wordpress?).with(:mixed).and_return(true).ordered

              expect { core.before_scan }.to_not raise_error
            end
          end
        end

        context 'when --force' do
          let(:cli_args) { "#{super()} --force" }

          it 'does not raise any error' do
            expect(core.target).to receive(:wordpress?).with(:mixed).and_return(false)

            expect { core.before_scan }.to_not raise_error
          end
        end
      end
    end
  end
end
