diff --git a/har2locust/__main__.py b/har2locust/__main__.py index a572d64..921c873 100644 --- a/har2locust/__main__.py +++ b/har2locust/__main__.py @@ -7,6 +7,7 @@ import sys import unicodedata from argparse import Namespace +from urllib.parse import urlencode import jinja2 @@ -132,6 +133,21 @@ def transform_payload_strings(entries: list[dict], args): request["postData"]["text"] = ast.unparse(ast.Constant(value=request["postData"]["text"])) +@entriesprocessor_with_args +def generate_text_from_url_encoded_params(entries: list[dict], args): + for entry in entries: + request = entry["request"] + if ( + "postData" in request + and request["postData"]["mimeType"] == "application/x-www-form-urlencoded" + and "text" not in request["postData"] + and "params" in request["postData"] + ): + params = [(param["name"], param["value"]) for param in request["postData"]["params"]] + text = f"'{urlencode(params)}'" + request["postData"]["text"] = text + + # Generate a valid identifier (https://docs.python.org/3.8/reference/lexical_analysis.html#identifiers) by replacing # invalid characters with "_". def generate_class_name(file_name: str) -> str: diff --git a/tests/inputs/url-encoded-body-no-text-field.har b/tests/inputs/url-encoded-body-no-text-field.har new file mode 100644 index 0000000..b8bce74 --- /dev/null +++ b/tests/inputs/url-encoded-body-no-text-field.har @@ -0,0 +1,129 @@ +{ + "log": { + "creator": { + "name": "Advanced REST Client", + "version": "Unknown" + }, + "version": "1.2", + "entries": [ + { + "startedDateTime": "2024-06-22T06:52:35.046Z", + "time": 551, + "cache": { + "afterRequest": null, + "beforeRequest": null, + "comment": "This application does not support caching." + }, + "timings": { + "blocked": 0, + "connect": 35, + "receive": 2, + "send": 1, + "wait": 510, + "dns": 3, + "ssl": 0 + }, + "request": { + "method": "POST", + "url": "https://reqres.in/api/users", + "httpVersion": "HTTP/1.1", + "headers": [ + { + "name": "Content-Type", + "value": "application/x-www-form-urlencoded" + } + ], + "bodySize": 23, + "headersSize": 51, + "cookies": [], + "queryString": [], + "postData": { + "mimeType": "application/x-www-form-urlencoded", + "params": [ + { + "name": "user", + "value": "test" + }, + { + "name": "password", + "value": "test" + } + ] + } + }, + "response": { + "status": 201, + "statusText": "Created", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Date", + "value": "Sat, 22 Jun 2024 06:52:35 GMT" + }, + { + "name": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "name": "Content-Length", + "value": "83" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Report-To", + "value": "{\"group\":\"heroku-nel\",\"max_age\":3600,\"endpoints\":[{\"url\":\"https://nel.heroku.com/reports?ts=1719039155&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=p%2BwJKtJXG3wXJzCb519GH6k%2FO6Y7IzJW46bg7IASsvw%3D\"}]}" + }, + { + "name": "Reporting-Endpoints", + "value": "heroku-nel=https://nel.heroku.com/reports?ts=1719039155&sid=c4c9725f-1ab0-44d8-820f-430df2718e11&s=p%2BwJKtJXG3wXJzCb519GH6k%2FO6Y7IzJW46bg7IASsvw%3D" + }, + { + "name": "Nel", + "value": "{\"report_to\":\"heroku-nel\",\"max_age\":3600,\"success_fraction\":0.005,\"failure_fraction\":0.05,\"response_headers\":[\"Via\"]}" + }, + { + "name": "X-Powered-By", + "value": "Express" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "name": "Etag", + "value": "W/\"53-zk0amioqgUdg6sIujyN26HqsClE\"" + }, + { + "name": "Via", + "value": "1.1 vegur" + }, + { + "name": "CF-Cache-Status", + "value": "DYNAMIC" + }, + { + "name": "Server", + "value": "cloudflare" + }, + { + "name": "CF-RAY", + "value": "897a50ff0e41a793-EZE" + } + ], + "headersSize": 828, + "bodySize": 83, + "content": { + "mimeType": "application/json", + "text": "{\"user\":\"test\",\"password\":\"test\",\"id\":\"642\",\"createdAt\":\"2024-06-22T06:52:35.431Z\"}", + "size": 83, + "encoding": "utf-8" + } + } + } + ] + } +} \ No newline at end of file diff --git a/tests/outputs/url-encoded-body-no-text-field.py b/tests/outputs/url-encoded-body-no-text-field.py new file mode 100644 index 0000000..7420023 --- /dev/null +++ b/tests/outputs/url-encoded-body-no-text-field.py @@ -0,0 +1,15 @@ +from locust import FastHttpUser, run_single_user, task + + +class url_encoded_body_no_text_field(FastHttpUser): + host = "https://reqres.in" + default_headers = {"Content-Type": "application/x-www-form-urlencoded"} + + @task + def t(self): + with self.client.request("POST", "/api/users", data="user=test&password=test", catch_response=True) as resp: + pass + + +if __name__ == "__main__": + run_single_user(url_encoded_body_no_text_field)