1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
|
# frozen_string_literal: true
require 'fileutils'
require 'tempfile'
require 'stringio'
module Rack
module Test
# Wraps a Tempfile with a content type. Including one or more UploadedFile's
# in the params causes Rack::Test to build and issue a multipart request.
#
# Example:
# post "/photos", "file" => Rack::Test::UploadedFile.new("me.jpg", "image/jpeg")
class UploadedFile
# The filename, *not* including the path, of the "uploaded" file
attr_reader :original_filename
# The tempfile
attr_reader :tempfile
# The content type of the "uploaded" file
attr_accessor :content_type
# Creates a new UploadedFile instance.
#
# Arguments:
# content :: is a path to a file, or an {IO} or {StringIO} object representing the content.
# content_type :: MIME type of the file
# binary :: Whether the file should be set to binmode (content treated as binary).
# original_filename :: The filename to use for the file. Required if content is StringIO, optional override if not
def initialize(content, content_type = 'text/plain', binary = false, original_filename: nil)
@content_type = content_type
@original_filename = original_filename
case content
when StringIO
initialize_from_stringio(content)
else
initialize_from_file_path(content)
end
@tempfile.binmode if binary
end
# The path to the tempfile. Will not work if the receiver's content is from a StringIO.
def path
tempfile.path
end
alias local_path path
# Delegate all methods not handled to the tempfile.
def method_missing(method_name, *args, &block)
tempfile.public_send(method_name, *args, &block)
end
# Append to given buffer in 64K chunks to avoid multiple large
# copies of file data in memory. Rewind tempfile before and
# after to make sure all data in tempfile is appended to the
# buffer.
def append_to(buffer)
tempfile.rewind
buf = String.new
buffer << tempfile.readpartial(65_536, buf) until tempfile.eof?
tempfile.rewind
nil
end
def respond_to_missing?(method_name, include_private = false) #:nodoc:
tempfile.respond_to?(method_name, include_private) || super
end
private
# Use the StringIO as the tempfile.
def initialize_from_stringio(stringio)
raise(ArgumentError, 'Missing `original_filename` for StringIO object') unless @original_filename
@tempfile = stringio
end
# Create a tempfile and copy the content from the given path into the tempfile, optionally renaming if
# original_filename has been set.
def initialize_from_file_path(path)
raise "#{path} file does not exist" unless ::File.exist?(path)
@original_filename ||= ::File.basename(path)
extension = ::File.extname(@original_filename)
@tempfile = Tempfile.new([::File.basename(@original_filename, extension), extension])
@tempfile.set_encoding(Encoding::BINARY)
FileUtils.copy_file(path, @tempfile.path)
end
end
end
end
|