RSpec.describe RubySMB::Dcerpc::Winreg do
  let(:winreg) do
    RubySMB::SMB1::Pipe.new(
      tree: double('Tree'),
      response: RubySMB::SMB1::Packet::NtCreateAndxResponse.new,
      name: 'winreg'
    )
  end

  describe '#open_root_key' do
    let(:root_key_request_packet)  { double('Root Key Request Packet') }
    let(:response)                 { double('Response') }
    let(:root_key_response_packet) { double('Root Key Response Packet') }
    let(:ph_key)                   { double('PHKEY') }
    before :example do
      allow(described_class::OpenRootKeyRequest).to receive(:new).and_return(root_key_request_packet)
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::OpenRootKeyResponse).to receive(:read).and_return(root_key_response_packet)
      allow(root_key_response_packet).to receive_messages(
        :error_status => WindowsError::Win32::ERROR_SUCCESS,
        :ph_key       => ph_key
      )
    end

    context 'when the root key is unknown' do
      it 'raises an ArgumentError exception' do
        expect { winreg.open_root_key('UNKNOWN') }.to raise_error(ArgumentError)
      end
    end

    it 'create the expected OpenRootKeyRequest packet' do
      winreg.open_root_key('HKLM')
      expect(described_class::OpenRootKeyRequest).to have_received(:new).with(opnum: described_class::OPEN_HKLM)
    end

    it 'sends the expected dcerpc request' do
      winreg.open_root_key('HKLM')
      expect(winreg).to have_received(:dcerpc_request).with(root_key_request_packet)
    end

    it 'creates a OpenRootKeyResponse structure from the expected dcerpc response' do
      winreg.open_root_key('HKLM')
      expect(described_class::OpenRootKeyResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::OpenRootKeyResponse).to receive(:read).and_raise(IOError)
        expect { winreg.open_root_key('HKLM') }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(root_key_response_packet).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.open_root_key('HKLM') }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected handler' do
      expect(winreg.open_root_key('HKLM')).to eq(ph_key)
    end
  end

  describe '#open_key' do
    let(:handle)                 { double('Handle') }
    let(:sub_key)                { double('Sub-key') }
    let(:openkey_request_packet) { double('OpenKey Request Packet') }
    let(:regsam)                 { double('Regsam') }
    let(:response)               { double('Response') }
    let(:open_key_response)      { double('OpenKey Response') }
    let(:phk_result)             { double('Phk Result') }
    before :example do
      allow(described_class::OpenKeyRequest).to receive(:new).and_return(openkey_request_packet)
      allow(openkey_request_packet).to receive(:sam_desired).and_return(regsam)
      allow(regsam).to receive_messages(
        :read_control=           => nil,
        :key_query_value=        => nil,
        :key_enumerate_sub_keys= => nil,
        :key_notify=             => nil,
      )
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::OpenKeyResponse).to receive(:read).and_return(open_key_response)
      allow(open_key_response).to receive_messages(
        :error_status => WindowsError::Win32::ERROR_SUCCESS,
        :phk_result   => phk_result
      )
    end

    it 'create the expected OpenKeyRequest packet' do
      winreg.open_key(handle, sub_key)
      expect(described_class::OpenKeyRequest).to have_received(:new).with(hkey: handle, lp_sub_key: sub_key)
    end

    it 'sets the expected user rights on the request packet' do
      winreg.open_key(handle, sub_key)
      expect(regsam).to have_received(:read_control=).with(1)
      expect(regsam).to have_received(:key_query_value=).with(1)
      expect(regsam).to have_received(:key_enumerate_sub_keys=).with(1)
      expect(regsam).to have_received(:key_notify=).with(1)
    end

    it 'sends the expected dcerpc request' do
      winreg.open_key(handle, sub_key)
      expect(winreg).to have_received(:dcerpc_request).with(openkey_request_packet)
    end

    it 'creates a OpenKeyResponse structure from the expected dcerpc response' do
      winreg.open_key(handle, sub_key)
      expect(described_class::OpenKeyResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::OpenKeyResponse).to receive(:read).and_raise(IOError)
        expect { winreg.open_key(handle, sub_key) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(open_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.open_key(handle, sub_key) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected handler' do
      expect(winreg.open_key(handle, sub_key)).to eq(phk_result)
    end
  end

  describe '#query_value' do
    let(:handle)                      { double('Handle') }
    let(:value_name)                  { double('Value Name') }
    let(:query_value_request_packet1) { double('Query Value Request Packet #1') }
    let(:query_value_request_packet2) { double('Query Value Request Packet #2') }
    let(:lp_data1)                    { double('LpData #1') }
    let(:lp_data2)                    { double('LpData #2') }
    let(:response1)                   { double('Response #1') }
    let(:response2)                   { double('Response #2') }
    let(:query_value_response1)       { double('Query Value Response #1') }
    let(:query_value_response2)       { double('Query Value Response #2') }
    let(:data)                        { double('Data') }
    let(:lpcb_data)                   { double('LpcbData') }
    let(:lpcb_data_referent)          { double('Lpcb Data Referent') }
    before :example do
      first_request = true
      allow(described_class::QueryValueRequest).to receive(:new) do
        if first_request
          first_request = false
          query_value_request_packet1
        else
          query_value_request_packet2
        end
      end
      allow(query_value_request_packet1).to receive(:lp_data).and_return(lp_data1)
      allow(query_value_request_packet2).to receive_messages(
        :lp_data    => lp_data2,
        :lpcb_data= => nil
      )
      allow(lp_data1).to receive(:referent_identifier=)
      allow(lp_data2).to receive(:max_count=)
      allow(winreg).to receive(:dcerpc_request).with(query_value_request_packet1).and_return(response1)
      allow(winreg).to receive(:dcerpc_request).with(query_value_request_packet2).and_return(response2)
      allow(described_class::QueryValueResponse).to receive(:read).with(response1).and_return(query_value_response1)
      allow(described_class::QueryValueResponse).to receive(:read).with(response2).and_return(query_value_response2)
      allow(query_value_response1).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
      allow(query_value_response2).to receive_messages(
        :error_status => WindowsError::Win32::ERROR_SUCCESS,
        :data         => data
      )
      allow(query_value_response1).to receive(:lpcb_data).and_return(lpcb_data)
      allow(lpcb_data).to receive(:referent).and_return(lpcb_data_referent)
    end

    it 'create the expected QueryValueRequest packets' do
      winreg.query_value(handle, value_name)
      expect(described_class::QueryValueRequest).to have_received(:new).with(hkey: handle, lp_value_name: value_name).twice
    end

    it 'sets the expected user rights on the first request packet' do
      winreg.query_value(handle, value_name)
      expect(lp_data1).to have_received(:referent_identifier=).with(0)
    end

    it 'sets the expected user rights on the second request packet' do
      winreg.query_value(handle, value_name)
      expect(lp_data2).to have_received(:max_count=).with(lpcb_data_referent)
      expect(query_value_request_packet2).to have_received(:lpcb_data=).with(lpcb_data)
    end

    it 'sends the expected dcerpc requests' do
      winreg.query_value(handle, value_name)
      expect(winreg).to have_received(:dcerpc_request).with(query_value_request_packet1).once.ordered
      expect(winreg).to have_received(:dcerpc_request).with(query_value_request_packet2).once.ordered
    end

    context 'when receiving the first response' do
      it 'creates a QueryValueResponse structure from the expected dcerpc response' do
        winreg.query_value(handle, value_name)
        expect(described_class::QueryValueResponse).to have_received(:read).with(response1)
      end

      context 'when an IOError occurs while parsing the response' do
        it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
          allow(described_class::QueryValueResponse).to receive(:read).with(response1).and_raise(IOError)
          expect { winreg.query_value(handle, value_name) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
        end
      end

      context 'when the first response error status is not WindowsError::Win32::ERROR_SUCCESS' do
        it 'raises a RubySMB::Dcerpc::Error::WinregError' do
          allow(query_value_response1).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
          expect { winreg.query_value(handle, value_name) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
        end
      end
    end

    context 'when receiving the second response' do
      it 'creates a QueryValueResponse structure from the expected dcerpc response' do
        winreg.query_value(handle, value_name)
        expect(described_class::QueryValueResponse).to have_received(:read).with(response2)
      end

      context 'when an IOError occurs while parsing the response' do
        it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
          allow(described_class::QueryValueResponse).to receive(:read).with(response2).and_raise(IOError)
          expect { winreg.query_value(handle, value_name) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
        end
      end

      context 'when the first response error status is not WindowsError::Win32::ERROR_SUCCESS' do
        it 'raises a RubySMB::Dcerpc::Error::WinregError' do
          allow(query_value_response2).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
          expect { winreg.query_value(handle, value_name) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
        end
      end
    end

    it 'returns the expected response data' do
      expect(winreg.query_value(handle, value_name)).to eq(data)
    end
  end

  describe '#close_key' do
    let(:handle)                   { double('Handle') }
    let(:close_key_request_packet) { double('CloseKey Request Packet') }
    let(:response)                 { double('Response') }
    let(:close_key_response)       { double('CloseKey Response') }
    before :example do
      allow(described_class::CloseKeyRequest).to receive(:new).and_return(close_key_request_packet)
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::CloseKeyResponse).to receive(:read).and_return(close_key_response)
      allow(close_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
    end

    it 'create the expected CloseKeyRequest packet' do
      winreg.close_key(handle)
      expect(described_class::CloseKeyRequest).to have_received(:new).with(hkey: handle)
    end

    it 'sends the expected dcerpc request' do
      winreg.close_key(handle)
      expect(winreg).to have_received(:dcerpc_request).with(close_key_request_packet)
    end

    it 'creates a CloseKeyResponse structure from the expected dcerpc response' do
      winreg.close_key(handle)
      expect(described_class::CloseKeyResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::CloseKeyResponse).to receive(:read).and_raise(IOError)
        expect { winreg.close_key(handle) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(close_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.close_key(handle) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected error status' do
      expect(winreg.close_key(handle)).to eq(WindowsError::Win32::ERROR_SUCCESS)
    end
  end

  describe '#query_info_key' do
    let(:handle)                        { double('Handle') }
    let(:query_info_key_request_packet) { double('CloseKey Request Packet') }
    let(:response)                      { double('Response') }
    let(:query_info_key_response)       { double('CloseKey Response') }
    before :example do
      allow(described_class::QueryInfoKeyRequest).to receive(:new).and_return(query_info_key_request_packet)
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::QueryInfoKeyResponse).to receive(:read).and_return(query_info_key_response)
      allow(query_info_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
    end

    it 'create the expected QueryInfoKeyRequest packet' do
      winreg.query_info_key(handle)
      expect(described_class::QueryInfoKeyRequest).to have_received(:new).with(hkey: handle)
    end

    it 'sends the expected dcerpc request' do
      winreg.query_info_key(handle)
      expect(winreg).to have_received(:dcerpc_request).with(query_info_key_request_packet)
    end

    it 'creates a QueryInfoKeyResponse structure from the expected dcerpc response' do
      winreg.query_info_key(handle)
      expect(described_class::QueryInfoKeyResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::QueryInfoKeyResponse).to receive(:read).and_raise(IOError)
        expect { winreg.query_info_key(handle) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(query_info_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.query_info_key(handle) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected response' do
      expect(winreg.query_info_key(handle)).to eq(query_info_key_response)
    end
  end

  describe '#enum_key' do
    let(:handle)                   { double('Handle') }
    let(:index)                    { double('Index') }
    let(:enum_key_request_packet)  { double('enum_key Request Packet') }
    let(:lp_class)                 { double('Lp Class') }
    let(:lp_name)                  { double('Lp Name') }
    let(:buffer)                   { double('Buffer') }
    let(:lp_class_buffer_referent) { double('Lp Class buffer referent') }
    let(:lp_name_buffer_referent)  { double('Lp Name buffer referent') }
    let(:response)                 { double('Response') }
    let(:enum_key_response)        { double('enum_key Response') }
    let(:result_str)               { double('Result String') }
    before :example do
      allow(described_class::EnumKeyRequest).to receive(:new).and_return(enum_key_request_packet)
      allow(enum_key_request_packet).to receive_messages(
        :lpft_last_write_time= => nil,
        :lp_class              => lp_class,
        :lp_name               => lp_name
      )
      allow(lp_class).to receive(:referent).and_return(lp_class_buffer_referent)
      allow(lp_name).to receive(:buffer).and_return(buffer)
      allow(buffer).to receive(:referent).and_return(lp_name_buffer_referent)
      allow(lp_class_buffer_referent).to receive(:buffer=)
      allow(lp_name_buffer_referent).to receive(:max_count=)
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::EnumKeyResponse).to receive(:read).and_return(enum_key_response)
      allow(enum_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
      allow(enum_key_response).to receive_message_chain(:lp_name, :to_s => result_str)
    end

    it 'create the expected EnumKeyRequest packet' do
      winreg.enum_key(handle, index)
      expect(described_class::EnumKeyRequest).to have_received(:new).with(hkey: handle, dw_index: index)
    end

    it 'sets the expected user rights on the request packet' do
      winreg.enum_key(handle, index)
      expect(enum_key_request_packet).to have_received(:lpft_last_write_time=).with(0)
      expect(lp_class_buffer_referent).to have_received(:buffer=).with(0)
      expect(lp_name_buffer_referent).to have_received(:max_count=).with(256)
    end

    it 'sends the expected dcerpc request' do
      winreg.enum_key(handle, index)
      expect(winreg).to have_received(:dcerpc_request).with(enum_key_request_packet)
    end

    it 'creates a EnumKeyResponse structure from the expected dcerpc response' do
      winreg.enum_key(handle, index)
      expect(described_class::EnumKeyResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::EnumKeyResponse).to receive(:read).and_raise(IOError)
        expect { winreg.enum_key(handle, index) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(enum_key_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.enum_key(handle, index) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected key name' do
      expect(winreg.enum_key(handle, index)).to eq(result_str)
    end
  end

  describe '#enum_value' do
    let(:handle)                    { double('Handle') }
    let(:index)                     { double('Index') }
    let(:enum_value_request_packet) { double('EnumValue Request Packet') }
    let(:lp_value_name)             { double('Lp Value Name') }
    let(:lp_data)                   { double('Lp Data') }
    let(:buffer)                    { double('Buffer') }
    let(:referent)                  { double('Referent') }
    let(:response)                  { double('Response') }
    let(:enum_value_response)       { double('EnumValue Response') }
    let(:result_str)                { double('Result String') }
    before :example do
      allow(described_class::EnumValueRequest).to receive(:new).and_return(enum_value_request_packet)
      allow(enum_value_request_packet).to receive_messages(
        :lp_value_name => lp_value_name,
        :lp_data       => lp_data
      )
      allow(lp_value_name).to receive(:buffer).and_return(buffer)
      allow(buffer).to receive(:referent).and_return(referent)
      allow(referent).to receive(:max_count=)
      allow(lp_data).to receive(:referent_identifier=)
      allow(winreg).to receive(:dcerpc_request).and_return(response)
      allow(described_class::EnumValueResponse).to receive(:read).and_return(enum_value_response)
      allow(enum_value_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_SUCCESS)
      allow(enum_value_response).to receive_message_chain(:lp_value_name, :to_s => result_str)
    end

    it 'create the expected EnumValueRequest packet' do
      winreg.enum_value(handle, index)
      expect(described_class::EnumValueRequest).to have_received(:new).with(hkey: handle, dw_index: index)
    end

    it 'sets the expected user rights on the request packet' do
      winreg.enum_value(handle, index)
      expect(referent).to have_received(:max_count=).with(256)
      expect(lp_data).to have_received(:referent_identifier=).with(0)
    end

    it 'sends the expected dcerpc request' do
      winreg.enum_value(handle, index)
      expect(winreg).to have_received(:dcerpc_request).with(enum_value_request_packet)
    end

    it 'creates a EnumValueResponse structure from the expected dcerpc response' do
      winreg.enum_value(handle, index)
      expect(described_class::EnumValueResponse).to have_received(:read).with(response)
    end

    context 'when an IOError occurs while parsing the response' do
      it 'raises a RubySMB::Dcerpc::Error::InvalidPacket' do
        allow(described_class::EnumValueResponse).to receive(:read).and_raise(IOError)
        expect { winreg.enum_value(handle, index) }.to raise_error(RubySMB::Dcerpc::Error::InvalidPacket)
      end
    end

    context 'when the response error status is not WindowsError::Win32::ERROR_SUCCESS' do
      it 'raises a RubySMB::Dcerpc::Error::WinregError' do
        allow(enum_value_response).to receive(:error_status).and_return(WindowsError::Win32::ERROR_INVALID_DATA)
        expect { winreg.enum_value(handle, index) }.to raise_error(RubySMB::Dcerpc::Error::WinregError)
      end
    end

    it 'returns the expected key name' do
      expect(winreg.enum_value(handle, index)).to eq(result_str)
    end
  end

  describe '#has_registry_key?' do
    let(:root_key)        { 'HKLM' }
    let(:sub_key)         { 'my\\sub\\key\\path' }
    let(:key)             { "#{root_key}\\#{sub_key}" }
    let(:root_key_handle) { double('Root Key Handle') }
    let(:subkey_handle)   { double('Subkey Handle') }
    before :example do
      allow(winreg).to receive_messages(
        :bind          => nil,
        :open_root_key => root_key_handle,
        :open_key      => subkey_handle,
        :close_key     => nil
      )
    end

    it 'binds a DCERPC connection to the expected remote endpoint' do
      winreg.has_registry_key?(key)
      expect(winreg).to have_received(:bind).with(endpoint: RubySMB::Dcerpc::Winreg)
    end

    it 'opens the expected root key' do
      winreg.has_registry_key?(key)
      expect(winreg).to have_received(:open_root_key).with(root_key)
    end

    it 'opens the expected registry key' do
      winreg.has_registry_key?(key)
      expect(winreg).to have_received(:open_key).with(root_key_handle, sub_key)
    end

    context 'when a WinregError occurs while opening the root key' do
      it 'returns false' do
        allow(winreg).to receive(:open_root_key).and_raise(RubySMB::Dcerpc::Error::WinregError)
        expect(winreg.has_registry_key?(key)).to be false
      end
    end

    context 'when a WinregError occurs while opening the registry key' do
      it 'returns false' do
        allow(winreg).to receive(:open_key).and_raise(RubySMB::Dcerpc::Error::WinregError)
        expect(winreg.has_registry_key?(key)).to be false
      end
    end

    it 'closes the key' do
      winreg.has_registry_key?(key)
      expect(winreg).to have_received(:close_key).with(subkey_handle)
    end

    it 'returns true when no error occurs' do
      expect(winreg.has_registry_key?(key)).to be true
    end
  end

  describe '#read_registry_key_value' do
    let(:root_key)        { 'HKLM' }
    let(:sub_key)         { 'my\\sub\\key\\path' }
    let(:key)             { "#{root_key}\\#{sub_key}" }
    let(:value_name)      { 'registry_value_name' }
    let(:root_key_handle) { double('Root Key Handle') }
    let(:subkey_handle)   { double('Subkey Handle') }
    let(:value)           { double('Value') }
    before :example do
      allow(winreg).to receive_messages(
        :bind          => nil,
        :open_root_key => root_key_handle,
        :open_key      => subkey_handle,
        :query_value   => value,
        :close_key     => nil
      )
    end

    it 'binds a DCERPC connection to the expected remote endpoint' do
      winreg.read_registry_key_value(key, value_name)
      expect(winreg).to have_received(:bind).with(endpoint: RubySMB::Dcerpc::Winreg)
    end

    it 'opens the expected root key' do
      winreg.read_registry_key_value(key, value_name)
      expect(winreg).to have_received(:open_root_key).with(root_key)
    end

    it 'opens the expected registry key' do
      winreg.read_registry_key_value(key, value_name)
      expect(winreg).to have_received(:open_key).with(root_key_handle, sub_key)
    end

    it 'queries the expected registry key value' do
      winreg.read_registry_key_value(key, value_name)
      expect(winreg).to have_received(:query_value).with(subkey_handle, value_name)
    end

    it 'closes the key' do
      winreg.read_registry_key_value(key, value_name)
      expect(winreg).to have_received(:close_key).with(subkey_handle)
    end

    it 'returns expect registry key value' do
      expect(winreg.read_registry_key_value(key, value_name)).to eq(value)
    end
  end

  describe '#enum_registry_key' do
    let(:root_key)                  { 'HKLM' }
    let(:sub_key)                   { 'my\\sub\\key\\path' }
    let(:key)                       { "#{root_key}\\#{sub_key}" }
    let(:value_name)                { 'registry_value_name' }
    let(:root_key_handle)           { double('Root Key Handle') }
    let(:subkey_handle)             { double('Subkey Handle') }
    let(:query_info_key_response)   { double('Query Info Key Response') }
    let(:subkey_nb)                 { 2 }
    before :example do
      allow(winreg).to receive_messages(
        :bind           => nil,
        :open_root_key  => root_key_handle,
        :open_key       => subkey_handle,
        :query_info_key => query_info_key_response,
        :enum_key       => nil,
        :close_key      => nil
      )
      allow(query_info_key_response).to receive(:lpc_sub_keys).and_return(subkey_nb)
    end

    it 'binds a DCERPC connection to the expected remote endpoint' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:bind).with(endpoint: RubySMB::Dcerpc::Winreg)
    end

    it 'opens the expected root key' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:open_root_key).with(root_key)
    end

    context 'when the registry key only contains the root key' do
      it 'queries information for the root key' do
        winreg.enum_registry_key(root_key)
        expect(winreg).to have_received(:query_info_key).with(root_key_handle)
      end
    end

    it 'opens the expected registry key' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:open_key).with(root_key_handle, sub_key)
    end

    it 'queries information for the expected registry key' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:query_info_key).with(subkey_handle)
    end

    it 'calls #enum_key the expected number of times' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:enum_key).with(subkey_handle, instance_of(Fixnum)).twice
    end

    it 'closes the key' do
      winreg.enum_registry_key(key)
      expect(winreg).to have_received(:close_key).with(subkey_handle)
    end

    it 'returns the expected array of enumerated keys' do
      key1 = 'key1'
      key2 = 'key2'
      allow(winreg).to receive(:enum_key).with(subkey_handle, 0).and_return(key1)
      allow(winreg).to receive(:enum_key).with(subkey_handle, 1).and_return(key2)
      expect(winreg.enum_registry_key(key)).to eq([key1, key2])
    end
  end

  describe '#enum_registry_values' do
    let(:root_key)                  { 'HKLM' }
    let(:sub_key)                   { 'my\\sub\\key\\path' }
    let(:key)                       { "#{root_key}\\#{sub_key}" }
    let(:value_name)                { 'registry_value_name' }
    let(:root_key_handle)           { double('Root Key Handle') }
    let(:subkey_handle)             { double('Subkey Handle') }
    let(:query_info_key_response)   { double('Query Info Key Response') }
    let(:subkey_nb)                 { 2 }
    before :example do
      allow(winreg).to receive_messages(
        :bind           => nil,
        :open_root_key  => root_key_handle,
        :open_key       => subkey_handle,
        :query_info_key => query_info_key_response,
        :enum_value     => nil,
        :close_key      => nil
      )
      allow(query_info_key_response).to receive(:lpc_values).and_return(subkey_nb)
    end

    it 'binds a DCERPC connection to the expected remote endpoint' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:bind).with(endpoint: RubySMB::Dcerpc::Winreg)
    end

    it 'opens the expected root key' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:open_root_key).with(root_key)
    end

    context 'when the registry key only contains the root key' do
      it 'queries information for the root key' do
        winreg.enum_registry_values(root_key)
        expect(winreg).to have_received(:query_info_key).with(root_key_handle)
      end
    end

    it 'opens the expected registry key' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:open_key).with(root_key_handle, sub_key)
    end

    it 'queries information for the expected registry key' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:query_info_key).with(subkey_handle)
    end

    it 'calls #enum_key the expected number of times' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:enum_value).with(subkey_handle, instance_of(Fixnum)).twice
    end

    it 'closes the key' do
      winreg.enum_registry_values(key)
      expect(winreg).to have_received(:close_key).with(subkey_handle)
    end

    it 'returns the expected array of enumerated keys' do
      value1 = 'value1'
      value2 = 'value2'
      allow(winreg).to receive(:enum_value).with(subkey_handle, 0).and_return(value1)
      allow(winreg).to receive(:enum_value).with(subkey_handle, 1).and_return(value2)
      expect(winreg.enum_registry_values(key)).to eq([value1, value2])
    end
  end
end
