RSpec.describe RubySMB::SMB1::File do

  let(:dispatcher) { RubySMB::Dispatcher::Socket.new(double('socket')) }
  let(:client) { RubySMB::Client.new(dispatcher, username: 'msfadmin', password: 'msfadmin') }
  let(:connect_response) {
    packet = RubySMB::SMB1::Packet::TreeConnectResponse.new
    packet.smb_header.tid = 2051
    packet.parameter_block.guest_access_rights.read("\xff\x01\x1f\x00")
    packet.parameter_block.access_rights.read("\xff\x01\x1f\x01")
    packet
  }
  let(:tree) { RubySMB::SMB1::Tree.new(client: client, share: '\\1.2.3.4\Share', response: connect_response) }
  let(:nt_create_andx_response) {
    response = RubySMB::SMB1::Packet::NtCreateAndxResponse.new
    response.parameter_block.ext_file_attributes = { normal: 1 }
    response.parameter_block.fid = 0x4000
    response.parameter_block.last_access_time = DateTime.parse("2017-09-20T1:1:1")
    response.parameter_block.last_change_time = DateTime.parse("2017-09-22T2:2:2")
    response.parameter_block.last_write_time  = DateTime.parse("2017-09-25T3:3:3")
    response.parameter_block.end_of_file = 53
    response.parameter_block.allocation_size = 4096
    response
  }
  let(:filename) { 'specfile.txt' }

  subject(:file) {
    described_class.new(tree: tree, response: nt_create_andx_response, name: filename)
  }

  it { is_expected.to respond_to :tree }
  it { is_expected.to respond_to :name }
  it { is_expected.to respond_to :attributes }
  it { is_expected.to respond_to :fid }
  it { is_expected.to respond_to :last_access }
  it { is_expected.to respond_to :last_change }
  it { is_expected.to respond_to :last_write }
  it { is_expected.to respond_to :size }
  it { is_expected.to respond_to :size_on_disk }

  it 'raises an exception when tree is no provided' do
    expect { described_class.new(tree: nil, response: nt_create_andx_response, name: filename) }.to raise_error(ArgumentError)
  end

  it 'raises an exception when response is no provided' do
    expect { described_class.new(tree: tree, response: nil, name: filename) }.to raise_error(ArgumentError)
  end

  it 'raises an exception when name is no provided' do
    expect { described_class.new(tree: tree, response: nt_create_andx_response, name: nil) }.to raise_error(ArgumentError)
  end

  it 'inherits the tree that spawned it' do
    expect(file.tree).to eq tree
  end

  it 'sets the file name to the name passed as argument' do
    expect(file.name).to eq filename
  end

  it 'inherits the attributes from the response packet' do
    expect(file.attributes).to eq nt_create_andx_response.parameter_block.ext_file_attributes
  end

  it 'inherits the file ID from the response packet' do
    expect(file.fid).to eq nt_create_andx_response.parameter_block.fid
  end

  it 'inherits the last_access from the response packet' do
    expect(file.last_access).to eq nt_create_andx_response.parameter_block.last_access_time.to_datetime
  end

  it 'inherits the last_change from the response packet' do
    expect(file.last_change).to eq nt_create_andx_response.parameter_block.last_change_time.to_datetime
  end

  it 'inherits the last_write from the response packet' do
    expect(file.last_write).to eq nt_create_andx_response.parameter_block.last_write_time.to_datetime
  end

  it 'inherits the size from the response packet' do
    expect(file.size).to eq nt_create_andx_response.parameter_block.end_of_file
  end

  it 'inherits the size_on_disk from the response packet' do
    expect(file.size_on_disk).to eq nt_create_andx_response.parameter_block.allocation_size
  end

  describe '#set_header_fields' do
    let(:request) { RubySMB::SMB1::Packet::ReadAndxRequest.new }
    it 'calls the set_header_field method from the Tree' do
      expect(tree).to receive(:set_header_fields).with(request).and_call_original
      file.set_header_fields(request)
    end

    it 'sets the packet file_id from the guid' do
      expect(file.set_header_fields(request).parameter_block.fid).to eq file.fid
    end
  end

  describe '#read_packet' do
    it 'creates a new ReadAndxRequest packet' do
      expect(RubySMB::SMB1::Packet::ReadAndxRequest).to receive(:new).and_call_original
      file.read_packet
    end

    it 'calls #set_header_fields to set ReadAndxRequest header fields' do
      request = RubySMB::SMB1::Packet::ReadAndxRequest.new
      allow(RubySMB::SMB1::Packet::ReadAndxRequest).to receive(:new).and_return(request)
      expect(file).to receive(:set_header_fields).with(request).and_call_original
      file.read_packet
    end

    it 'sets the #max_count_of_bytes_to_return of the packet' do
      expect(file.read_packet(read_length: 55).parameter_block.max_count_of_bytes_to_return).to eq 55
    end

    it 'sets the #min_count_of_bytes_to_return of the packet' do
      expect(file.read_packet(read_length: 55).parameter_block.min_count_of_bytes_to_return).to eq 55
    end
    
    it 'sets the #remaining of the packet' do
      expect(file.read_packet(read_length: 55).parameter_block.remaining).to eq 55
    end

    it 'sets the offset of the packet' do
      expect(file.read_packet(offset: 55).parameter_block.offset).to eq 55
    end
  end

  describe '#read' do
    let(:read_data) { 'read data' }
    let(:raw_response) { double('fake raw response data') }
    let(:read_andx_response) {
      res = RubySMB::SMB1::Packet::ReadAndxResponse.new
      res.data_block.data = read_data
      res
    }

    before :example do
      allow(client).to receive(:send_recv).and_return(raw_response)
      allow(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response).and_return(read_andx_response)
    end

    context 'when the number of bytes to read is not provided' do
      it 'reads #size bytes by default' do
        expect(file).to receive(:read_packet).with(read_length: file.size, offset: 0).once.and_call_original
        expect(file.read).to eq(read_data)
      end
    end

    context 'when the number of bytes to read is less than or equal to max_buffer_size' do
      it 'reads only one packet with the number of bytes provided as argument' do
        client.max_buffer_size = read_data.size
        expect(file).to receive(:read_packet).with(read_length: read_data.size, offset: 0).once.and_call_original
        expect(file.read(bytes: read_data.size)).to eq(read_data)
      end
    end

    context 'when the number of bytes to read is greater than max_buffer_size' do
      it 'reads multiple packets with at most max_buffer_size bytes per chunk' do
        client.max_buffer_size = read_data.size - 1
        read_io = StringIO.new(read_data)
        expect(file).to receive(:read_packet).with(read_length: client.max_buffer_size, offset: 0).once.ordered.and_call_original
        expect(file).to receive(:read_packet).with(read_length: (read_data.size - client.max_buffer_size), offset: client.max_buffer_size).once.ordered.and_call_original
        allow(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response) do
          read_andx_response.data_block.data = read_io.read(client.max_buffer_size)
          read_andx_response
        end
        expect(file.read(bytes: read_data.size)).to eq(read_data)
      end
    end

    context 'when sending the request packet and gets a response back' do
      context 'when the response is not valid' do
        it 'raise an InvalidPacket exception' do
          read_andx_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
          expect { file.read }.to raise_error(RubySMB::Error::InvalidPacket)
        end
      end

      context 'when the response status code is not STATUS_SUCCESS' do
        it 'raise an UnexpectedStatusCode exception' do
          read_andx_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
          expect { file.read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
        end
      end

      context 'when the response is an EmptyPacket with the SMB_COM_READ_ANDX command and STATUS_SUCCESS status code' do
        let(:empty_packet) do
          empty_packet = RubySMB::SMB1::Packet::EmptyPacket.new
          empty_packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_READ_ANDX
          empty_packet.original_command = RubySMB::SMB1::Commands::SMB_COM_READ_ANDX
          empty_packet
        end

        it 'returns an empty string if it is the first request' do
          allow(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response).and_return(empty_packet)
          expect(file.read).to eq('')
        end

        it 'returns the current read data if it happens after the first request' do
          partial_data = read_data[0..-2]
          client.max_buffer_size = partial_data.size
          first_request = true
          allow(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response).twice do
            if first_request
              read_andx_response.data_block.data = partial_data
              first_request = false
              read_andx_response
            else
              empty_packet
            end
          end
          expect(file.read(bytes: read_data.size)).to eq(partial_data)
        end
      end
    end
  end

  describe '#write_packet' do
    it 'creates a new WriteAndxRequest packet' do
      expect(RubySMB::SMB1::Packet::WriteAndxRequest).to receive(:new).and_call_original
      file.write_packet
    end

    it 'calls #set_header_fields to set WriteAndxRequest headers' do
      request = RubySMB::SMB1::Packet::WriteAndxRequest.new
      allow(RubySMB::SMB1::Packet::WriteAndxRequest).to receive(:new).and_return(request)
      expect(file).to receive(:set_header_fields).with(request).and_call_original
      file.write_packet
    end

    it 'sets the offset of the packet' do
      expect(file.write_packet(offset: 55).parameter_block.offset).to eq 55
    end

    it 'sets the write_mode to writethrough_mode' do
      expect(file.write_packet.parameter_block.write_mode.writethrough_mode).to eq 1
    end

    it 'sets the remaining number of bytes to data_length value' do
      data = 'write data'
      write_andx_request = file.write_packet(data: data)
      expect(write_andx_request.parameter_block.remaining).to eq write_andx_request.parameter_block.data_length
      expect(write_andx_request.parameter_block.remaining).to eq data.size
    end

    it 'sets the data of the packet' do
      data = 'write data'
      expect(file.write_packet(data: data).data_block.data).to eq data
    end
  end

  describe '#write' do
    let(:write_data) { 'write data' }
    let(:raw_response) { double('fake raw response data') }
    let(:write_andx_response) do
      response = RubySMB::SMB1::Packet::WriteAndxResponse.new
      response.parameter_block.count_low = write_data.size
      response
    end

    before :example do
      allow(client).to receive(:send_recv).and_return(raw_response)
      allow(RubySMB::SMB1::Packet::WriteAndxResponse).to receive(:read).with(raw_response).and_return(write_andx_response)
    end

    describe 'offset' do
      it 'writes from offset 0 by default' do
        expect(file).to receive(:write_packet).with(data: write_data, offset: 0).and_call_original
        file.write(data: write_data)
      end

      it 'writes from the offset passed as arguement' do
        offset = 10
        expect(file).to receive(:write_packet).with(data: write_data, offset: offset).and_call_original
        file.write(data: write_data, offset: offset)
      end
    end

    context 'when the buffer size is less than or equal to max_buffer_size' do
      it 'sends only one packet with the entire buffer' do
        client.max_buffer_size = write_data.size
        expect(file).to receive(:write_packet).with(data: write_data, offset: 0).once.and_call_original
        expect(file.write(data: write_data)).to eq write_data.size
      end
    end

    context 'when the buffer size is greater than max_buffer_size' do
      it 'sends multiple packets with at most max_buffer_size bytes per chunk' do
        client.max_buffer_size = write_data.size - 1
        first_data_chunk = write_data[0, client.max_buffer_size]
        second_data_chunk = write_data[client.max_buffer_size..-1]
        original_write_packet = file.method(:write_packet)

        expect(file).to receive(:write_packet).with(data: first_data_chunk , offset: 0).once do
          allow(RubySMB::SMB1::Packet::WriteAndxResponse).to receive(:read).with(raw_response) do
            write_andx_response.parameter_block.count_low = first_data_chunk.size
            write_andx_response
          end
          original_write_packet.call(data: first_data_chunk, offset: 0)
        end
        expect(file).to receive(:write_packet).with(data: second_data_chunk, offset: client.max_buffer_size).once do
          allow(RubySMB::SMB1::Packet::WriteAndxResponse).to receive(:read).with(raw_response) do
            write_andx_response.parameter_block.count_low = second_data_chunk.size
            write_andx_response
          end
          original_write_packet.call(data: second_data_chunk, offset: client.max_buffer_size)
        end
        expect(file.write(data: write_data)).to eq write_data.size
      end
    end

    context 'when sending the request packet and gets a response back' do
      context 'when the response is not a WriteAndxResponse packet' do
        it 'raise an InvalidPacket exception' do
          write_andx_response.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_ECHO
          expect { file.write(data: write_data) }.to raise_error(RubySMB::Error::InvalidPacket)
        end
      end

      context 'when the response status code is not STATUS_SUCCESS' do
        it 'raise an UnexpectedStatusCode exception' do
          write_andx_response.smb_header.nt_status = WindowsError::NTStatus::STATUS_INVALID_HANDLE.value
          expect { file.write(data: write_data) }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
        end
      end
    end
  end

  describe '#append' do
    it 'calls #write with the expected data and the offset set to the end of the file (file size)' do
      data = 'append data'
      expect(file).to receive(:write).with(data: data, offset: file.size)
      file.append(data: data)
    end
  end

  describe '#delete' do
    let(:set_info_res) { RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse.new }

    before :example do
      allow(client).to receive(:send_recv)
      allow(RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse).to receive(:read).and_return(set_info_res)
    end

    it 'calls #delete_packet' do
      expect(file).to receive(:delete_packet)
      file.delete
    end

    it 'returns the response NTStatus code' do
      status = WindowsError::NTStatus::STATUS_SUCCESS
      set_info_res.smb_header.nt_status = status.value
      expect(file.delete).to eq status
    end

    it 'raises an InvalidPacket exception if the response is not valid' do
      allow(set_info_res).to receive(:valid?).and_return(false)
      expect { file.delete }.to raise_error(RubySMB::Error::InvalidPacket)
    end
  end

  describe '#delete_packet' do
    let(:request) do
      request = RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest.new
      passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION +
        RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
      request.data_block.trans2_parameters.information_level = passthrough_info_level
      request
    end

    before :example do
      allow(RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest).to receive(:new).and_return(request)
    end

    it 'creates a new Trans2 SetFileInformationRequest packet' do
      expect(RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest).to receive(:new).and_return(request)
      file.delete_packet
    end

    it 'calls Tree #set_header_fields to set SetFileInformationRequest headers' do
      expect(tree).to receive(:set_header_fields).with(request).and_call_original
      file.delete_packet
    end

    it 'sets the Trans2 parameter #fid of the packet with the File FID' do
      fid = 0x1000
      file.fid = fid
      expect(file.delete_packet.data_block.trans2_parameters.fid).to eq fid
    end

    it 'sets the Trans2 parameter #information_level of the packet' do
      request.data_block.trans2_parameters.information_level = 0
      passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_DISPOSITION_INFORMATION +
        RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
      expect(file.delete_packet.data_block.trans2_parameters.information_level).to eq passthrough_info_level
    end

    it 'sets the File Information #delete_pending field of the packet' do
      expect(file.delete_packet.data_block.trans2_data.info_level_struct.delete_pending).to eq 1
    end

    it 'sets the Trans2 ParameterBlock fields' do
      parameter_block = file.delete_packet.parameter_block
      expect(parameter_block.total_parameter_count).to eq request.parameter_block.parameter_count
      expect(parameter_block.total_data_count).to eq request.parameter_block.data_count
      expect(parameter_block.max_parameter_count).to eq request.parameter_block.parameter_count
      expect(parameter_block.max_data_count).to eq 16_384
    end
  end

  describe '#rename' do
    let(:set_info_res) { RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse.new }
    let(:filename) { 'file.txt' }

    before :example do
      allow(client).to receive(:send_recv)
      allow(RubySMB::SMB1::Packet::Trans2::SetFileInformationResponse).to receive(:read).and_return(set_info_res)
    end

    it 'calls #rename_packet' do
      expect(file).to receive(:rename_packet)
      file.rename(filename)
    end

    it 'returns the response NTStatus code' do
      status = WindowsError::NTStatus::STATUS_SUCCESS
      set_info_res.smb_header.nt_status = status.value
      expect(file.rename(filename)).to eq status
    end

    it 'raises an InvalidPacket exception if the response is not valid' do
      allow(set_info_res).to receive(:valid?).and_return(false)
      expect { file.rename(filename) }.to raise_error(RubySMB::Error::InvalidPacket)
    end
  end

  describe '#rename_packet' do
    let(:request) do
      request = RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest.new
      passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_RENAME_INFORMATION +
        RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
      request.data_block.trans2_parameters.information_level = passthrough_info_level
      request
    end

    before :example do
      allow(RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest).to receive(:new).and_return(request)
    end

    it 'creates a new Trans2 SetFileInformationRequest packet' do
      expect(RubySMB::SMB1::Packet::Trans2::SetFileInformationRequest).to receive(:new).and_return(request)
      file.rename_packet(filename)
    end

    it 'calls Tree #set_header_fields to set SetFileInformationRequest headers' do
      expect(tree).to receive(:set_header_fields).with(request).and_call_original
      file.rename_packet(filename)
    end

    it 'sets the Trans2 parameter #fid of the packet with the File FID' do
      fid = 0x1000
      file.fid = fid
      expect(file.rename_packet(filename).data_block.trans2_parameters.fid).to eq fid
    end

    it 'sets the Trans2 parameter #information_level of the packet' do
      request.data_block.trans2_parameters.information_level = 0
      passthrough_info_level = RubySMB::Fscc::FileInformation::FILE_RENAME_INFORMATION +
        RubySMB::Fscc::FileInformation::SMB_INFO_PASSTHROUGH
      expect(file.rename_packet(filename).data_block.trans2_parameters.information_level).to eq passthrough_info_level
    end

    it 'sets the File Information #rename_pending field of the packet' do
      expect(file.rename_packet(filename).data_block.trans2_data.info_level_struct.file_name).to eq filename.encode('utf-16le').force_encoding('ASCII-8BIT')
    end

    it 'sets the Trans2 ParameterBlock fields' do
      parameter_block = file.rename_packet(filename).parameter_block
      expect(parameter_block.total_parameter_count).to eq request.parameter_block.parameter_count
      expect(parameter_block.total_data_count).to eq request.parameter_block.data_count
      expect(parameter_block.max_parameter_count).to eq request.parameter_block.parameter_count
      expect(parameter_block.max_data_count).to eq 16_384
    end
  end

  describe '#close' do
    let(:request)  { double('CloseRequest') }
    let(:response) { double('CloseResponse') }
    let(:raw_response) { double('Raw response') }

    before :example do
      allow(RubySMB::SMB1::Packet::CloseRequest).to receive(:new).and_return(request)
      allow(file).to receive(:set_header_fields).and_return(request)
      allow(client).to receive(:send_recv).and_return(raw_response)
      allow(RubySMB::SMB1::Packet::CloseResponse).to receive(:read).and_return(response)
      allow(response).to receive(:valid?).and_return(true)
      allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_SUCCESS)
    end

    it 'creates a new SMB1 CloseRequest packet' do
      expect(RubySMB::SMB1::Packet::CloseRequest).to receive(:new)
      file.close
    end

    it 'calls Tree #set_header_fields to set SetFileInformationRequest headers' do
      expect(file).to receive(:set_header_fields).with(request)
      file.close
    end

    it 'calls Client #send_recv with the expected request' do
      expect(client).to receive(:send_recv).with(request)
      file.close
    end

    it 'parses the response as a SMB1 CloseResponse packet' do
      expect(RubySMB::SMB1::Packet::CloseResponse).to receive(:read).with(raw_response)
      file.close
    end

    it 'raises an InvalidPacket exception if the response is not valid' do
      allow(response).to receive(:valid?).and_return(false)
      smb_header = double('SMB Header')
      allow(response).to receive(:smb_header).and_return(smb_header)
      allow(smb_header).to receive_messages(:protocol => nil, :command => nil)
      expect { file.close }.to raise_error(RubySMB::Error::InvalidPacket)
    end

    it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
      allow(response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
      expect { file.close }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
    end

    it 'returns the response status code' do
      expect(file.close).to eq WindowsError::NTStatus::STATUS_SUCCESS
    end
  end

  describe '#send_recv_read' do
    let(:read_data) { 'read data' }
    let(:raw_response) { double('fake raw response data') }
    let(:read_andx_response) {
      res = RubySMB::SMB1::Packet::ReadAndxResponse.new
      res.data_block.data = read_data
      res
    }

    before :example do
      allow(client).to receive(:send_recv).and_return(raw_response)
      allow(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response).and_return(read_andx_response)
    end

    context 'when the number of bytes to read is not provided' do
      it 'reads 0 bytes by default' do
        expect(file).to receive(:read_packet).with(read_length: 0, offset: 0).once.and_call_original
        file.send_recv_read
      end
    end

    it 'only reads the number of bytes provided as argument' do
      bytes = 5
      expect(file).to receive(:read_packet).with(read_length: bytes, offset: 0).once.and_call_original
      file.send_recv_read(read_length: bytes)
    end

    it 'reads from the offset provided as argument' do
      offset = 3
      expect(file).to receive(:read_packet).with(read_length: 0, offset: offset).once.and_call_original
      file.send_recv_read(offset: offset)
    end

    it 'calls Client #send_recv with the expected request' do
      request = double('Request')
      allow(file).to receive(:read_packet).and_return(request)
      expect(client).to receive(:send_recv).with(request)
      file.send_recv_read
    end

    it 'parses the response as a SMB1 ReadAndxResponse packet' do
      expect(RubySMB::SMB1::Packet::ReadAndxResponse).to receive(:read).with(raw_response)
      file.send_recv_read
    end

    it 'raises an InvalidPacket exception if the response is not valid' do
      allow(read_andx_response).to receive(:valid?).and_return(false)
      expect { file.send_recv_read }.to raise_error(RubySMB::Error::InvalidPacket)
    end

    it 'raises an UnexpectedStatusCode exception if the response status code is not STATUS_SUCCESS' do
      allow(read_andx_response).to receive(:status_code).and_return(WindowsError::NTStatus::STATUS_OBJECT_NAME_NOT_FOUND)
      expect { file.send_recv_read }.to raise_error(RubySMB::Error::UnexpectedStatusCode)
    end

    it 'returns the expected string' do
      expect(file.send_recv_read).to eq(read_data)
    end
  end

  describe '#send_recv_write' do
    let(:write_data) { 'write data' }
    let(:request) { double('Request') }
    let(:raw_response) { double('fake raw response data') }
    let(:write_andx_response) {
      res = RubySMB::SMB1::Packet::WriteAndxResponse.new
      res.parameter_block.count_low = write_data.size
      res
    }

    before :example do
      allow(file).to receive(:write_packet).and_return(request)
      allow(request).to receive(:set_64_bit_offset)
      allow(client).to receive(:send_recv).and_return(raw_response)
      allow(RubySMB::SMB1::Packet::WriteAndxResponse).to receive(:read).with(raw_response).and_return(write_andx_response)
    end

    it 'reads 0 bytes from offset 0 by default' do
      expect(file).to receive(:write_packet).with(data: '', offset: 0).once.and_call_original
      file.send_recv_write
    end

    it 'writes the data provided as argument' do
      expect(file).to receive(:write_packet).with(data: write_data, offset: 0).once.and_call_original
      file.send_recv_write(data: write_data)
    end

    it 'reads from the offset provided as argument' do
      offset = 3
      expect(file).to receive(:write_packet).with(data: '', offset: offset).once.and_call_original
      file.send_recv_write(offset: offset)
    end

    it 'sets the 64 bit offset to true' do
      expect(request).to receive(:set_64_bit_offset).with(true)
      file.send_recv_write
    end

    it 'calls Client #send_recv with the expected request' do
      expect(client).to receive(:send_recv).with(request)
      file.send_recv_write
    end

    it 'parses the response as a SMB1 WriteAndxResponse packet' do
      expect(RubySMB::SMB1::Packet::WriteAndxResponse).to receive(:read).with(raw_response)
      file.send_recv_write
    end

    it 'raises an InvalidPacket exception if the response is not valid' do
      allow(write_andx_response).to receive(:valid?).and_return(false)
      expect { file.send_recv_write }.to raise_error(RubySMB::Error::InvalidPacket)
    end

    it 'returns the expected response #count_low value' do
      expect(file.send_recv_write).to eq(write_data.size)
    end
  end
end

