require 'spec_helper'
require 'nmap/xml'

describe XML do
  let(:path) { File.expand_path('spec/scan.xml') }

  subject { described_class.new(path) }

  describe "#initialize" do
    context "when given a Nokogiri::XML::Document" do
      let(:document) { Nokogiri::XML(File.read(path)) }

      it "should use the document" do
        expect(described_class.new(document).version).to eq(subject.version)
      end
    end

    context "when given an IO object" do
      let(:io) { File.new(path) }

      it "should parse the IO object" do
        expect(described_class.new(io).version).to eq(subject.version)
      end
    end

    context "when given a String" do
      it "should parse the file at the path" do
        expect(described_class.new(path).version).to eq(subject.version)
      end
    end
  end

  describe "parse" do
    let(:xml) { File.read(path) }

    subject { described_class.parse(xml) }

    it "should return an XML object" do
      expect(subject).to be_kind_of(described_class)
    end

    it "should parse the XML" do
      expect(subject.version).to_not be_nil
    end
  end

  describe "open" do
    subject { described_class.open(path) }

    it "should return an XML object" do
      expect(subject).to be_kind_of(described_class)
    end

    it "should parse the XML" do
      expect(subject.version).to_not be_nil
    end
  end

  describe "#version" do
    it "should have a version" do
      expect(subject.version).to eq('1.04')
    end
  end

  describe "#scanner" do
    it "should parse the scanner version" do
      subject.scanner.version == '4.68'
    end

    it "should parse the scanner name" do
      expect(subject.scanner.name).to eq('nmap')
    end

    it "should parse the scanner arguments" do
      expect(subject.scanner.arguments).to eq('nmap -v -sS -sU -A -O -oX spec/scan.xml scanme.nmap.org')
    end

    it "should parse the scanner start time" do
      expect(subject.scanner.start_time).to be_kind_of(Time)
    end
  end

  describe "#scan_info" do
    subject { super().scan_info.first }

    it "should parse the type" do
      expect(subject.type).to eq(:syn)
    end

    it "should parse the protocol" do
      expect(subject.protocol).to eq(:tcp)
    end

    it "should parse the services" do
      expect(subject.services).not_to be_empty
    end
  end

  it "should parse the verbose level" do
    expect(subject.verbose).to eq(1)
  end

  it "should parse the debugging level" do
    expect(subject.debugging).to eq(0)
  end

  describe "#each_run_stat" do
    subject { super().each_run_stat.first }

    it "should yield RunStat objects" do
      expect(subject).to be_kind_of(RunStat)
    end

    it "should parse the end time" do
      expect(subject.end_time).to be_kind_of(Time)
    end

    it "should parse the time elapsed" do
      expect(subject.elapsed).not_to be_nil
    end

    it "should parse the summary" do
      expect(subject.summary).not_to be_empty
    end

    it "should parse the exit status" do
      expect(subject.exit_status).to eq('success').or eq('failure')
    end
  end

  describe "#run_stats" do
    subject { super().run_stats }

    it { is_expected.not_to be_empty }
    it { is_expected.to all(be_kind_of(RunStat)) }
  end

  describe "#each_task" do
    subject { super().each_task.first }

    it "should parse task name" do
      expect(subject.name).to eq('Ping Scan')
    end

    it "should parse the start time" do
      expect(subject.start_time).to be_kind_of(Time)
    end

    it "should parse the end time" do
      expect(subject.end_time).to be_kind_of(Time)
      expect(subject.end_time).to be > subject.start_time
    end

    it "should parse the extrainfo" do
      expect(subject.extrainfo).not_to be_empty
    end
  end

  describe "#tasks" do
    subject { super().tasks }

    it { is_expected.not_to be_empty }
    it { is_expected.to all(be_kind_of(ScanTask)) }
  end

  describe "#each_host" do
    subject { super().each_host.first }

    it "should yield Host objects" do
      expect(subject).to be_kind_of(Host)
    end
  end

  describe "#hosts" do
    subject { super().hosts }

    it { is_expected.not_to be_empty }
    it { expect(subject).to all(be_kind_of(Host)) }
  end

  describe "#host" do
    subject { super().host }

    it { expect(subject).to be_kind_of(Host) }
  end

  describe "#down_host" do
    pending "need down hosts in scan.xml" do
      subject { super().down_host }

      it { is_expected.to be_kind_of(Host) }

      it "should contain only up hosts" do
        expect(subject.status.state).to be(:down)
      end
    end
  end

  describe "#each_up_host" do
    subject { super().each_up_host.first }

    it "should yield Host objects" do
      expect(subject).to be_kind_of(Host)
    end

    it "should be up" do
      expect(subject.status.state).to eq(:up)
    end
  end

  describe "#up_hosts" do
    subject { super().up_hosts }

    it { is_expected.not_to be_empty }
    it { is_expected.to all(be_kind_of(Host)) }

    it "should contain only up hosts" do
      expect(subject.all? { |host| host.status.state == :up }).to be_truthy
    end
  end

  describe "#up_host" do
    subject { super().up_host }

    it { is_expected.to be_kind_of(Host) }

    it "should contain only up hosts" do
      expect(subject.status.state).to be(:up)
    end
  end

  describe "#each" do
    it "should iterate over each up host" do
      expect(subject.each.all? { |host| host.status.state == :up }).to eq(true)
    end
  end

  describe "#to_s" do
    it "should convert to a String" do
      expect(subject.to_s).to eq(path)
    end
  end

  describe "#inspect" do
    it "should include the class and path" do
      expect(subject.inspect).to eq("#<#{described_class}: #{path}>")
    end
  end
end
