require production_code

describe Snmpjr::Walker do
  let(:target) { double :target }
  let(:oid) { Snmpjr::Wrappers::SMI::OID.new '1.2.3.4.5.6' }
  let(:session) { double(:session).as_null_object }

  let(:pdu_factory) { double Snmpjr::Wrappers::Util::DefaultPDUFactory }
  let(:tree_util) { double Snmpjr::Wrappers::Util::TreeUtils }

  subject { described_class.new(target: target, session: session) }

  let(:vb1) { Snmpjr::Wrappers::SMI::VariableBinding.new(
    Snmpjr::Wrappers::SMI::OID.new('1.3.6'),
    Snmpjr::Wrappers::SMI::OctetString.new('Sample Result 1'))
  }

  let(:vb2) { Snmpjr::Wrappers::SMI::VariableBinding.new(
    Snmpjr::Wrappers::SMI::OID.new('1.3.6.1'),
    Snmpjr::Wrappers::SMI::OctetString.new('Sample Result 2'))
  }
  let(:tree_event_1) { double :tree_event_1, {variable_bindings: [vb1], is_error?: false} }
  let(:tree_event_2) { double :tree_event_2, {variable_bindings: [vb2], is_error?: false} }
  let(:example_event_responses) { [tree_event_1, tree_event_2] }

  before do
    allow(Snmpjr::Wrappers::Util::DefaultPDUFactory).to receive(:new).and_return pdu_factory

    allow(Snmpjr::Wrappers::Util::TreeUtils).to receive(:new).with(session.snmp, pdu_factory).and_return tree_util
    allow(tree_util).to receive(:getSubtree).with(target, oid).and_return example_event_responses
  end

  describe '#walk' do
    it 'starts an snmp session' do
      expect(session).to receive(:start)
      subject.walk  oid
    end

    context 'when an exception is raised' do
      before do
        allow(tree_util).to receive(:getSubtree).with(target, oid).and_raise Exception.new 'noAccess'
      end

      it 'raises a runtime error' do
        expect{
          subject.walk oid
        }.to raise_error RuntimeError
      end
    end

    context 'when a target times out' do
      let(:tree_event_1) { double :tree_event_1 }

      before do
        allow(tree_event_1).to receive(:is_error?).and_return true
        allow(tree_event_1).to receive(:error_message).and_return 'Request timed out.'
      end

      it 'raises a timeout error' do
        expect{
          subject.walk oid
        }.to raise_error Snmpjr::TargetTimeoutError
      end
    end

    context 'when a random error occurs' do
      let(:tree_event_1) { double :tree_event_1 }

      before do
        allow(tree_event_1).to receive(:is_error?).and_return true
        allow(tree_event_1).to receive(:error_message).and_return 'noAccess'
      end

      it 'raises a runtime error' do
        expect{
          subject.walk oid
        }.to raise_error RuntimeError
      end
    end

    context 'when a walk returns no variable bindings' do
      let(:tree_event) { double :tree_event, {variable_bindings: [], is_error?: false } }
      let(:example_event_responses) { [tree_event] }

      it 'returns an empty array' do
        expect(subject.walk oid).to eq []
      end
    end

    it 'performs a synchronous walk' do
      expect(subject.walk oid).to match_array [Snmpjr::Response.new(oid: vb1.oid.to_s, value: vb1.variable.to_s, type: vb1.variable.syntax_string),
                                               Snmpjr::Response.new(oid: vb2.oid.to_s, value: vb2.variable.to_s, type: vb1.variable.syntax_string)]
    end

    it 'closes the snmp session' do
      expect(session).to receive(:close)
      subject.walk oid
    end
  end
end
