diff --git a/HISTORY.md b/HISTORY.md index ec95c29f..4cd557f7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,14 @@ # Release History +## 2.1.1 (2019-04-11) + +**Features** + +refactor upload files mechanism with [requests-toolbelt](https://toolbelt.readthedocs.io/en/latest/user.html#multipart-form-data-encoder): + +- simplify usage syntax, detect mimetype with [filetype](https://github.com/h2non/filetype.py). +- support upload multiple fields. + ## 2.1.0 (2019-04-10) **Features** diff --git a/Pipfile b/Pipfile index 83a9df29..5e4c9ad5 100644 --- a/Pipfile +++ b/Pipfile @@ -11,6 +11,7 @@ har2case = "*" colorama = "*" colorlog = "*" requests-toolbelt = "*" +filetype = "*" [dev-packages] Flask = "<1.0.0" diff --git a/httprunner/__about__.py b/httprunner/__about__.py index 01d4dc4f..cc08e19b 100644 --- a/httprunner/__about__.py +++ b/httprunner/__about__.py @@ -1,7 +1,7 @@ __title__ = 'HttpRunner' __description__ = 'One-stop solution for HTTP(S) testing.' __url__ = 'https://github.com/HttpRunner/HttpRunner' -__version__ = '2.1.0' +__version__ = '2.1.1' __author__ = 'debugtalk' __author_email__ = 'mail@debugtalk.com' __license__ = 'Apache-2.0' diff --git a/httprunner/built_in.py b/httprunner/built_in.py index e3e0d1bd..7f26e8ea 100644 --- a/httprunner/built_in.py +++ b/httprunner/built_in.py @@ -12,13 +12,18 @@ import re import string import time +import filetype from httprunner.compat import basestring, builtin_str, integer_types, str from httprunner.exceptions import ParamsError from requests_toolbelt import MultipartEncoder +PWD = os.getcwd() + + +############################################################################### +## built-in functions +############################################################################### -""" built-in functions -""" def gen_random_string(str_len): """ generate random string with specified length """ @@ -38,24 +43,68 @@ def get_current_date(fmt="%Y-%m-%d"): """ return datetime.datetime.now().strftime(fmt) -def multipart_encoder(field_name, file_path, file_type=None, file_headers=None): - if not os.path.isabs(file_path): - file_path = os.path.join(os.getcwd(), file_path) - filename = os.path.basename(file_path) - with open(file_path, 'rb') as f: - fields = { - field_name: (filename, f.read(), file_type) - } +############################################################################### +## upload files with requests-toolbelt +# e.g. +# - test: +# name: upload file +# variables: +# file_path: "data/test.env" +# multipart_encoder: ${multipart_encoder(file=$file_path)} +# request: +# url: /post +# method: POST +# headers: +# Content-Type: ${multipart_content_type($multipart_encoder)} +# data: $multipart_encoder +# validate: +# - eq: ["status_code", 200] +# - startswith: ["content.files.file", "UserName=test"] +############################################################################### + +def multipart_encoder(**kwargs): + """ initialize MultipartEncoder with uploading fields. + """ + def get_filetype(file_path): + file_type = filetype.guess(file_path) + if file_type: + return file_type.mime + else: + return "text/html" + + fields_dict = {} + for key, value in kwargs.items(): + + if os.path.isabs(value): + _file_path = value + is_file = True + else: + global PWD + _file_path = os.path.join(PWD, value) + is_file = os.path.isfile(_file_path) + + if is_file: + filename = os.path.basename(_file_path) + with open(_file_path, 'rb') as f: + mime_type = get_filetype(_file_path) + fields_dict[key] = (filename, f.read(), mime_type) + else: + fields_dict[key] = value + + return MultipartEncoder(fields=fields_dict) - return MultipartEncoder(fields) def multipart_content_type(multipart_encoder): + """ prepare Content-Type for request headers + """ return multipart_encoder.content_type -""" built-in comparators -""" +############################################################################### +## built-in comparators +############################################################################### + def equals(check_value, expect_value): assert check_value == expect_value diff --git a/httprunner/loader.py b/httprunner/loader.py index ade04d6a..3fddb238 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -8,8 +8,7 @@ import os import sys import yaml -from httprunner import exceptions, logger, parser, utils, validator - +from httprunner import built_in, exceptions, logger, parser, utils, validator ############################################################################### ## file loader @@ -263,7 +262,6 @@ def load_module_functions(module): def load_builtin_functions(): """ load built_in module functions """ - from httprunner import built_in return load_module_functions(built_in) @@ -703,6 +701,7 @@ def load_project_tests(test_path, dot_env_path=None): # locate PWD and load debugtalk.py functions project_mapping["PWD"] = project_working_directory + built_in.PWD = project_working_directory project_mapping["functions"] = debugtalk_functions # load api diff --git a/httprunner/runner.py b/httprunner/runner.py index 5df077b5..d5f96a6d 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -267,7 +267,7 @@ class Runner(object): self.session_context.update_session_variables(extracted_variables_mapping) # validate - validators = test_dict.get("validate", []) + validators = test_dict.get("validate") or test_dict.get("validators") or [] try: self.session_context.validate(validators, resp_obj) except (exceptions.ParamsError, exceptions.ValidationFailure, exceptions.ExtractFailure): diff --git a/setup.py b/setup.py index 3c8effee..9e118d2b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ install_requires = [ "har2case", "colorama", "colorlog", - "requests_toolbelt" + "requests_toolbelt", + "filetype" ] class UploadCommand(Command): diff --git a/tests/httpbin/load_image.yml b/tests/httpbin/load_image.yml index 1243ef86..4ea6da75 100644 --- a/tests/httpbin/load_image.yml +++ b/tests/httpbin/load_image.yml @@ -7,7 +7,7 @@ request: url: /image/png method: GET - validators: + validate: - eq: ["status_code", 200] - test: @@ -15,7 +15,7 @@ request: url: /image/jpeg method: GET - validators: + validate: - eq: ["status_code", 200] - test: @@ -23,7 +23,7 @@ request: url: /image/webp method: GET - validators: + validate: - eq: ["status_code", 200] - test: @@ -31,6 +31,6 @@ request: url: /image/svg method: GET - validators: + validate: - eq: ["status_code", 200] diff --git a/tests/httpbin/upload.yml b/tests/httpbin/upload.yml index 3abd8c76..344b90bd 100644 --- a/tests/httpbin/upload.yml +++ b/tests/httpbin/upload.yml @@ -5,17 +5,15 @@ - test: name: upload file variables: - field_name: "file" - file_path: "LICENSE" - file_type: "text/html" - multipart_encoder: ${multipart_encoder($field_name, $file_path, $file_type)} + file_path: "data/test.env" + multipart_encoder: ${multipart_encoder(file=$file_path)} request: url: /post method: POST headers: Content-Type: ${multipart_content_type($multipart_encoder)} data: $multipart_encoder - validators: + validate: - eq: ["status_code", 200] - - startswith: ["content.files.file", "MIT License"] + - startswith: ["content.files.file", "UserName=test"]