require 'msf/core'

describe Msf::Exploit::Remote::BrowserAutopwn2 do



  #
  # Recreate the environment (framework, mixins, etc)
  #



  def mock_note_destroy
    # The destroy method doesn't pass the note as an argument startlike framework.jobs_stop_job.
    # So here's I'm just gonna clear them all, and that sort of mimics #destroy.
    framework = double('Msf::Framework', datastore: {})

    # This empties it
    notes = []

    db = double('db1')
    allow(db).to receive(:notes).and_return(notes)
    allow(framework).to receive(:db).and_return(db)

    allow(subject).to receive(:framework).and_return(framework)
  end

  def mock_report_note(arg)
    framework = double('Msf::Framework', datastore: {})
    notes = [create_fake_note('bap.clicks')]
    db = double('db2')
    allow(db).to receive(:notes).and_return(notes)
    allow(framework).to receive(:db).and_return(db)
    allow(subject).to receive(:framework).and_return(framework)
  end

  def create_fake_note(tag, data='')
    note = double('note3')

    allow(note).to receive(:ntype).and_return(tag)
    allow(note).to receive(:data).and_return(data)
    allow(note).to receive(:destroy) { mock_note_destroy }

    note
  end

  def mock_stop_job(arg)
    framework = double('Msf::Framework', datastore: {})
    jobs = subject.framework.jobs.delete_if {|e| e.first == arg.to_s}
    allow(jobs).to receive(:stop_job) { |arg| mock_stop_job(arg) }
    allow(framework).to receive(:jobs).and_return(jobs)
    allow(subject).to receive(:framework).and_return(framework)
  end

  def create_fake_job(id)
    ctx = double('ctx')
    handler = create_fake_windows_meterpreter
    allow(ctx).to receive(:first).and_return(handler)
    job = [id.to_s, double('job')]
    allow(job).to receive(:ctx).and_return(ctx)

    job
  end

  def create_fake_exploit(opts={})
    full_name         = opts[:full_name]
    short_name        = opts[:short_name]
    rank              = opts[:rank]            || 400
    disclosure_date   = opts[:disclosure_date] || 'Dec 21 2014'
    compat_payloads   = opts[:compat_payloads] || []
    datastore_options = opts[:datastore_options] || {}
    job_id            = opts[:job_id] || 0
    requirements      = opts[:requirements] || {}
    workspace         = opts[:workspace] || 'default'

    mod = Msf::Exploit.new
    mod.extend(Msf::Exploit::Remote::BrowserExploitServer)

    allow(mod).to receive(:fullname).and_return(full_name)
    allow(mod).to receive(:rank).and_return(rank)
    allow(mod).to receive(:disclosure_date).and_return(disclosure_date)
    allow(mod).to receive(:compatible_payloads).and_return(compat_payloads)
    allow(mod).to receive(:datastore).and_return(datastore_options)
    allow(mod).to receive(:job_id).and_return(job_id)
    allow(mod).to receive(:workspace).and_return(workspace)
    allow(mod).to receive(:exploit_simple)
    allow(mod).to receive(:vprint_status)
    allow(mod).to receive(:shortname).and_return(short_name)
    mod.instance_variable_set(:@requirements, requirements)

    mod
  end

  def create_fake_multi_handler
    compat_payloads = [
      [windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter]
    ]

    create_fake_exploit(
      full_name: 'multi/handler',
      short_name: 'multi/handler',
      compat_payloads: compat_payloads,
      job_id: 0,
    )
  end

  def create_fake_ms14_064
    compat_payloads = [
      [windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter]
    ]

    create_fake_exploit(
      full_name: 'windows/browser/ms14_064_ole_code_execution',
      short_name: 'ms14_064_ole_code_execution',
      rank: 600,
      disclosure_date: 'Nov 13 2014',
      compat_payloads: compat_payloads,
      datastore_options: {'URIPATH'=>'/ms14_64'},
      job_id: 0,
      requirements: {os_name: windows_81_regex}
    )
  end

  def create_fake_flash_net_connection_confusion
    compat_payloads = [
      [windows_meterpreter_reverse_tcp, create_fake_windows_meterpreter],
      [linux_meterpreter_reverse_tcp, create_fake_linux_meterpreter]
    ]

    create_fake_exploit(
      full_name: 'multi/browser/adobe_flash_net_connection_confusion',
      short_name: 'adobe_flash_net_connection_confusion',
      rank: 500,
      disclosure_date: 'Mar 12 2015',
      compat_payloads: compat_payloads,
      datastore_options: {'URIPATH'=>'/flash1'},
      job_id: 1,
      requirements: {os_name: lambda { |ver| ver == '16.0.0.305' }}
    )
  end

  def create_fake_flash_uncompress_zlib_uaf
    compat_payloads = [windows_meterpreter_reverse_tcp, linux_meterpreter_reverse_tcp]

    create_fake_exploit(
      full_name: 'multi/browser/adobe_flash_uncompress_zlib_uaf',
      short_name: 'adobe_flash_uncompress_zlib_uaf',
      rank: 500,
      disclosure_date: 'Apr 28 2014',
      compat_payloads: compat_payloads,
      datastore_options: {'URIPATH'=>'/flash2'},
      job_id: 2,
      requirements: {os_name: lambda { |ver| ver == '16.0.0.287' }}
    )
  end

  def create_fake_payload(opts={})
    platforms  = opts[:platforms]
    archs      = opts[:archs]
    datastores = opts[:datastore_options]
    fullname   = opts[:fullname]
    shortname  = opts[:shortname]
    workspace  = opts[:workspace] || 'default'

    p = Msf::Payload.new

    platforms.each do |platform|
      p.platform.platforms << platform
    end

    archs.each do |arch|
      p.arch << arch
    end

    datastores.each_pair do |key, value|
      p.datastore[key] = value
    end

    datastores['WORKSPACE'] = workspace

    allow(p).to receive(:fullname).and_return(fullname)
    allow(p).to receive(:shoftname).and_return(shortname)
    allow(p).to receive(:workspace).and_return(workspace)
    allow(p).to receive(:exploit_simple)

    p
  end

  def create_fake_windows_meterpreter
    create_fake_payload(
      platforms: [Msf::Module::Platform::Windows],
      archs: ['x86'],
      datastore_options: {'LPORT'=>'4444'},
      fullname: windows_meterpreter_reverse_tcp,
      shortname: 'reverse_tcp'
    )
  end

  def create_fake_linux_meterpreter
    create_fake_payload(
      platforms: [Msf::Module::Platform::Linux],
      archs: ['x86'],
      datastore_options: {'LPORT'=>'4445'},
      fullname: linux_meterpreter_reverse_tcp,
      shortname: 'reverse_tcp'
    )
  end

  def mock_payload_create(full_name)
    full_name.gsub!(/^firefox/, 'generic')
    available_payloads.each do |p|
      return p if p.fullname == full_name
    end

    nil
  end

  def mock_exploit_create(full_name)
    available_exploits.each do |x|
      return x if x.fullname == full_name
    end

    nil
  end

  let(:windows_meterpreter_reverse_tcp) do
    'windows/meterpreter/reverse_tcp'
  end

  let(:linux_meterpreter_reverse_tcp) do
    'linux/x86/meterpreter/reverse_tcp'
  end

  let(:windows_81_regex) do
    /^(?:Microsoft )?Windows 8\.1/
  end

  let(:job_ids) do
    ids = []

    subject.framework.jobs.each do |job|
      ids << job.first.to_i
    end

    ids
  end

  let(:available_exploits) do
    @exploits ||= lambda {
      exploits = []

      exploits << create_fake_ms14_064
      exploits << create_fake_flash_uncompress_zlib_uaf
      exploits << create_fake_flash_net_connection_confusion
      exploits << create_fake_multi_handler

      exploits
    }.call
  end

  let(:available_payloads) do
    @payloads ||= lambda {
        payloads = []

        payloads << create_fake_windows_meterpreter
        payloads << create_fake_linux_meterpreter

        payloads
      }.call
  end

  let(:fake_exploit_hash) do
    exploits = {}

    available_exploits.each do |x|
      exploits[x.fullname.to_s] = '__SYMBOLIC__'
    end

    exploits
  end

  let(:autopwn_datastore_options) do
    {
      'SRVHOST'               => '0.0.0.0',
      'SRVPORT'               => 8080,
      'MaxExploits'           => 20,
      'LHOST'                 => '127.0.0.1',
      'SSL'                   => false,
      'MaxSessions'           => -1,
      'Custom404'             => 'http://example.com',
      'PAYLOAD_ANDROID'       => 'android/meterpreter/reverse_tcp',
      'PAYLOAD_FIREFOX'       => 'firefox/shell_reverse_tcp',
      'PAYLOAD_GENERIC'       => 'generic/shell_reverse_tcp',
      'PAYLOAD_JAVA'          => 'java/meterpreter/reverse_tcp',
      'PAYLOAD_LINUX'         => 'linux/x86/meterpreter/reverse_tcp',
      'PAYLOAD_OSX'           => 'osx/x86/shell_reverse_tcp',
      'PAYLOAD_UNIX'          => 'cmd/unix/reverse',
      'PAYLOAD_WIN'           => 'windows/meterpreter/reverse_tcp',
      'PAYLOAD_ANDROID_LPORT' => 4443,
      'PAYLOAD_FIREFOX_LPORT' => 4442,
      'PAYLOAD_GENERIC_LPORT' => 4459,
      'PAYLOAD_JAVA_LPORT'    => 4448,
      'PAYLOAD_LINUX_LPORT'   => 4445,
      'PAYLOAD_OSX_LPORT'     => 4447,
      'PAYLOAD_UNIX_LPORT'    => 4446,
      'PAYLOAD_WIN_LPORT'     => 4444
    }
  end

  let(:default_profile_data) do
    {
      "BAP.1433806920.Client.blLGFIlwYrxfvcY" => {
        "source"      => "script",
        "os_name"     => "Windows 8.1",
        "os_vendor"   => "undefined",
        "os_device"   => "undefined",
        "ua_name"     => "Firefox",
        "ua_ver"      => "35.0",
        "arch"        => "x86",
        "java"        => "1.7",
        "silverlight" => "false",
        "flash"       => "14.0",
        "vuln_test"   => "true",
        "proxy"       => false,
        "language"    => "en-US,en;q=0.5",
        "tried"       => true
    }}
  end

  let(:profile_tag) do
    default_profile_data.keys.first.split('.')[3]
  end

  let(:note_type_prefix) do
    default_profile_data.keys.first.split('.')[0,3] * "."
  end

  let(:cli) do
    c = double('cli')
    allow(c).to receive(:peerhost).and_return('127.0.0.1')
    c
  end

  let(:cli_req) do
    req = Rex::Proto::Http::Request.new
    req.headers['Cookie'] = "__ua=#{profile_tag}"
    req
  end

  before(:each) do
    framework = double('Msf::Framework', datastore: {})

    # Prepare fake notes
    notes = [create_fake_note("#{note_type_prefix}.#{profile_tag}", default_profile_data)]

    # Prepare framework.db
    w = double('workspace')
    allow(w).to receive(:name).and_return('WORKSPACE')

    db = double('db')
    allow(db).to receive(:report_note).with(kind_of(Hash)) { |arg| mock_report_note(arg) }
    allow(db).to receive(:notes).and_return(notes)
    allow(db).to receive(:active).and_return(true)
    allow(db).to receive(:workspace).and_return(w)
    allow(framework).to receive(:db).and_return(db)

    # Prepare framework.exploits
    exploits = double('exploits')
    allow(exploits).to receive(:create) { |arg| mock_exploit_create(arg) }
    allow(exploits).to receive(:each_pair).and_yield(available_exploits[0].fullname, '__SYMBOLIC__').and_yield(available_exploits[1].fullname, '__SYMBOLIC__').and_yield(available_exploits[2].fullname, '__SYMBOLIC__')
    allow(framework).to receive(:exploits).and_return(exploits)

    # Prepare jobs
    jobs = {'0' => create_fake_job(0)}
    allow(jobs).to receive(:stop_job) { |arg| mock_stop_job(arg) }
    allow(framework).to receive(:jobs).and_return(jobs)

    # Prepare payloads
    payloads = {}
    available_payloads.each do |p|
      payloads[p.fullname] = "__SYMBOLIC__"
    end
    allow(payloads).to receive(:create) { |arg| mock_payload_create(arg) }
    allow(framework).to receive(:payloads).and_return(payloads)

    allow_any_instance_of(described_class).to receive(:cli).and_return(cli)

    allow_any_instance_of(described_class).to receive(:framework).and_return(framework)
    allow_any_instance_of(described_class).to receive(:report_note) { |arg| mock_report_note(arg) }
  end

  subject do
    mod = Msf::Exploit::Remote.allocate
    mod.extend described_class
    mod.send(:initialize)
    mod.send(:datastore=, autopwn_datastore_options)
    allow(mod).to receive(:fullname).and_return('multi/browser/autopwn')
    allow(mod).to receive(:datastore).and_return(autopwn_datastore_options)
    mod
  end



  #
  # Method testing starts here
  #



  describe '#init_exploits' do
    before(:each) do
      allow(subject).to receive(:set_exploit_options)
      subject.instance_variable_set(:@bap_exploits, [])
    end

    context 'when two exploits are loaded' do
      it 'saves two exploits in instance variable @bap_exploits' do
        subject.init_exploits
        expect(subject.instance_variable_get(:@bap_exploits).length).to eq(3)
      end
    end
  end

  context 'when removing jobs' do
    describe '#rm_exploit_jobs' do
      before(:each) do
        subject.instance_variable_set(:@exploit_job_ids, job_ids)
      end

      it 'empties jobs' do
        expect(subject.framework.jobs.length).to eq(1)
        subject.rm_exploit_jobs
        expect(subject.framework.jobs).to be_empty
      end
    end

    describe '#rm_payload_jobs' do
      it 'empties jobs' do
        subject.instance_variable_set(:@payload_job_ids, job_ids)
        expect(subject.framework.jobs.length).to eq(1)
        subject.rm_payload_jobs
        expect(subject.framework.jobs).to be_empty
      end
    end
  end

  describe '#set_exploit_options' do
    before(:each) do
      payload_info = {
        payload_name: windows_meterpreter_reverse_tcp,
        payload_lport: 4444
      }

      allow(subject).to receive(:select_payload).and_return([payload_info])
    end

    let(:xploit) do
      create_fake_ms14_064
    end

    context 'when a module is given' do
      before(:each) do
        subject.instance_variable_set(:@bap_exploits, [])
        subject.init_exploits
        subject.set_exploit_options(xploit)
      end

      it 'sets the datastore options' do
        expect(xploit.datastore['URIPATH']).to match(/^\/\w+/)
      end

    end
  end

  describe '#is_resource_taken?' do
    before(:each) do
      allow(subject).to receive(:set_exploit_options)
      subject.instance_variable_set(:@bap_exploits, [])
      subject.init_exploits
    end

    let(:repeated_resource) { '/flash1' }

    let(:non_repeated_resource) { '/unique' }

    context 'when a resource is repeated' do
      it 'returns true' do
        expect(subject.is_resource_taken?(repeated_resource)).to be_truthy
      end
    end

    context 'when a resource is not repeated' do
      it 'returns false' do
        expect(subject.is_resource_taken?(non_repeated_resource)).to be_falsey
      end
    end
  end

  describe '#assign_module_resource' do
    it 'returns a resource' do
      allow(subject).to receive(:is_resource_taken?).and_return(false)
      expect(subject.assign_module_resource).to match(/^\w+$/)
    end
  end


  context 'when sorting' do
    before(:each) do
      allow(subject).to receive(:set_exploit_options)
      subject.instance_variable_set(:@bap_exploits, [])
      subject.init_exploits
    end

    let(:bap_groups) { subject.group_bap_modules }

    describe '#sort_date_in_group' do
      it 'returns modules sorted by date' do
        unsorted_first = 'multi/browser/adobe_flash_uncompress_zlib_uaf'
        expect(bap_groups[500].first.fullname).to eq(unsorted_first)

        sorted_first = 'multi/browser/adobe_flash_net_connection_confusion'
        expect(subject.sort_date_in_group(bap_groups)[500].first.fullname).to eq(sorted_first)
      end
    end

    describe '#sort_group_by_rank' do
      it 'returns modules sorted by rank' do
        unsorted_order = [0, 100, 200, 300, 400, 500, 600]
        sorted_order = [0, 100, 200, 300, 400, 500, 600].reverse

        expect(bap_groups.keys).to eq(unsorted_order)
        expect(subject.sort_group_by_rank(bap_groups).keys).to eq(sorted_order)
      end
    end

    describe '#group_bap_modules' do
      it 'returns modules sorted by group' do
        group_order = [0, 100, 200, 300, 400, 500, 600]
        expect(bap_groups.keys).to eq(group_order)
      end
    end

    describe '#finalize_sorted_modules' do
      context 'when MaxExploits is 1' do
        it 'returns one exploit' do
          expect(subject.instance_variable_get(:@bap_exploits).length).to eq(3)

          allow(subject).to receive(:datastore).and_return({'MaxExploitCount'=>1})
          subject.finalize_sorted_modules(bap_groups)
          expect(subject.instance_variable_get(:@bap_exploits).length).to eq(1)
        end
      end
    end
  end

  describe '#get_selected_payload_name' do
    context 'when windows platform is given' do
      it 'returns windows/meterpreter/reverse_tcp' do
        expect(subject.get_selected_payload_name('win')).to eq(windows_meterpreter_reverse_tcp)
      end
    end
  end

  describe '#get_selected_payload_lport' do
    context 'when windows platform is given' do
      it 'returns 4444' do
        expect(subject.get_selected_payload_lport('win')).to eq(4444)
      end
    end
  end

  describe '#get_payload_lhost' do
    it 'returns LHOST' do
      expect(subject.get_payload_lhost).to eq(autopwn_datastore_options['LHOST'])
    end
  end

  describe '#start_payload_listeners' do
    let(:active_payload) do
      create_fake_windows_meterpreter
    end

    let(:wanted_payloads) do
      [{
        payload_name: active_payload.fullname,
        payload_lport: active_payload.datastore['LPORT']
      }]
    end

    before(:each) do
      subject.instance_variable_set(:@wanted_payloads, wanted_payloads)
      subject.instance_variable_set(:@payload_job_ids, [])
    end

    context 'when a payload is listening' do
      it 'adds the job ID to the payload job ID list' do
        expect(subject.instance_variable_get(:@payload_job_ids).length).to eq(0)
        subject.start_payload_listeners
        expect(subject.instance_variable_get(:@payload_job_ids).length).to eq(1)
      end
    end
  end

  describe '#parse_rank' do
    context 'when rank is 600' do
      it 'returns Excellent' do
        expect(subject.parse_rank(600)).to eq('Excellent')
      end
    end

    context 'when rank is 500' do
      it 'returns Great' do
        expect(subject.parse_rank(500)).to eq('Great')
      end
    end

    context 'when rank is 400' do
      it 'returns Good' do
        expect(subject.parse_rank(400)).to eq('Good')
      end
    end

    context 'when rank is 300' do
      it 'returns Good' do
        expect(subject.parse_rank(300)).to eq('Normal')
      end
    end

    context 'when rank is 200' do
      it 'returns Average' do
        expect(subject.parse_rank(200)).to eq('Average')
      end
    end

    context 'when rank is 100' do
      it 'returns Low' do
        expect(subject.parse_rank(100)).to eq('Low')
      end
    end

    context 'when rank is 0' do
      it 'returns Manual' do
        expect(subject.parse_rank(0)).to eq('Manual')
      end
    end
  end

  describe '#is_payload_platform_compatible?' do
    let(:windows_payload) { create_fake_windows_meterpreter }

    context 'when a valid platform is given' do
      it 'returns true' do
        expect(subject.is_payload_platform_compatible?(windows_payload, 'win')).to be_truthy
      end
    end

    context 'when an invalid platform is given' do
      it 'returns false' do
        expect(subject.is_payload_platform_compatible?(windows_payload, 'linux')).to be_falsey
      end
    end
  end

  describe '#is_payload_compatible?' do
    let(:windows_exploit_payloads) { create_fake_ms14_064.compatible_payloads }

    context 'when a valid payload name is given' do
      it 'returns true' do
        expect(subject.is_payload_compatible?(windows_exploit_payloads, windows_meterpreter_reverse_tcp)).to be_truthy
      end
    end

    context 'when an invalid payload name is given' do
      it 'returns false' do
        expect(subject.is_payload_compatible?(windows_exploit_payloads, linux_meterpreter_reverse_tcp)).to be_falsey
      end
    end
  end

  describe '#is_multi_platform_exploit?' do
    context 'when a windows exploit is given' do
      it 'returns false' do
        windows_exploit = create_fake_ms14_064
        expect(subject.is_multi_platform_exploit?(windows_exploit)).to be_falsey
      end
    end

    context 'when a multi-platform flash exploit is given' do
      it 'returns true' do
        flash_exploit = create_fake_flash_net_connection_confusion
        expect(subject.is_multi_platform_exploit?(flash_exploit)).to be_truthy
      end
    end
  end

  describe '#select_payload' do
    before(:each) do
      subject.instance_variable_set(:@wanted_payloads, [])
    end

    context 'when a ms14_064 is given' do
      it 'returns a windows payload' do
        m = create_fake_ms14_064
        expected_payload = m.compatible_payloads.first.first
        selected_payload = subject.select_payload(m)
        expect(selected_payload.length).to eq(1)
        expect(selected_payload.first[:payload_name]).to eq(expected_payload)
      end
    end
  end

  describe '#start_exploits' do
    before(:each) do
      allow(subject).to receive(:set_exploit_options)
      subject.instance_variable_set(:@exploit_job_ids, [])
      subject.instance_variable_set(:@bap_exploits, [])
      subject.init_exploits
    end

    it 'returns job IDs of the exploits started' do
      subject.start_exploits
      available_exploits.each do |x|
        expect(subject.instance_variable_get(:@exploit_job_ids)).to include(x.job_id)
      end
    end
  end

  describe 'when outputing' do

    # Get stdout:
    # http://stackoverflow.com/questions/11349270/test-output-to-command-line-with-rspec
    def get_stdout(&block)
      out = $stdout
      $stdout = fake = StringIO.new
      begin
        yield
      ensure
        $stdout = out
      end
      fake.string
    end

    before(:each) do
      allow(subject).to receive(:print_status) { |arg| $stdout.puts arg }
      allow(subject).to receive(:print_line) { |arg| $stdout.puts arg }
      allow(subject).to receive(:print) { |arg| $stdout.puts arg }
      allow(subject).to receive(:set_exploit_options)
      subject.instance_variable_set(:@bap_exploits, [])
      subject.init_exploits
    end

    describe '#show_ready_exploits' do

      let(:output) { get_stdout { subject.show_ready_exploits } }

      context 'when there is ms14_064_ole_code_execution available' do
        it 'shows ms14_064_ole_code_execution on the table' do
          expect(output).to include('ms14_064_ole_code_execution')
        end
      end

      context 'when there is adobe_flash_uncompress_zlib_uaf available' do
        it 'shows adobe_flash_uncompress_zlib_uaf' do
          expect(output).to include('adobe_flash_uncompress_zlib_uaf')
        end
      end

      context 'when there is adobe_flash_net_connection_confusion available' do
        it 'shows adobe_flash_net_connection_confusion' do
          expect(output).to include('adobe_flash_net_connection_confusion')
        end
      end
    end

    skip '#start_service' do
      it 'prints the BrowserAutopwn URL' do
        # This code blows up, por que??
        # 3 threads exist(s) when only 1 thread expected after suite runs
        allow_any_instance_of(Msf::Exploit::Remote::BrowserExploitServer).to receive(:super)
        allow(subject).to receive(:show_ready_exploits)
        allow_any_instance_of(Rex::Socket).to receive(:source_address).and_return(nil)
        subject.start_service
      end
    end

  end

  describe '#log_click' do
    let(:ip) { '192.168.1.123' }

    context 'when a link is clicked' do
      it 'reports a bap.clicks note' do
        subject.log_click(ip)
        expect(subject.framework.db.notes.first.ntype).to eq('bap.clicks')
      end
    end
  end

  describe '#get_exploit_urls' do
    context 'when ms14_064 is on the list' do
      it 'returns the URL for ms14_064' do
        ms14_064 = create_fake_ms14_064
        allow(subject).to receive(:get_suitable_exploits).and_return([ms14_064])
        expect(subject.get_exploit_urls(cli, cli_req).first).to include(ms14_064.datastore['URIPATH'])
      end
    end
  end

  describe '#is_ip_targeted?' do
    let(:ip) { '1.2.3.4' }

    context 'when IP 1.2.3.4 is on the whitelist' do
      it 'returns true' do
        subject.instance_variable_set(:@whitelist, [ip])
        expect(subject.is_ip_targeted?(ip)).to be_truthy
      end
    end

    context 'when IP 1.2.3.4 is not on the whitelist' do
      it 'returns false' do
        subject.instance_variable_set(:@whitelist, [])
        expect(subject.is_ip_targeted?(ip)).to be_falsey
      end
    end
  end

  describe '#get_custom_404_url' do
    it 'returns a custom 404' do
      expect(subject.get_custom_404_url).to eq(autopwn_datastore_options['Custom404'])
    end
  end

  skip '#configure_job_output' do
  end

  describe '#session_count' do
    it 'returns the total session count' do
      subject.instance_variable_set(:@payload_job_ids, job_ids)
      expect(subject.session_count).to eq(0)
    end
  end


end