From 3d67710411a5ea7adab91e6ee3af3b42866190f7 Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Fri, 11 Jul 2025 13:51:19 +0200 Subject: [PATCH 1/7] updated format of datastore templates --- test/proxy/read_from_proxies.json | 12 ++++++------ test/proxy/write_to_proxies.json | 6 +++--- test/response/format/csv/check_store_values.json | 6 +++--- test/response/format/csv/csv_requests.json | 16 ++++++++-------- .../format/csv/preserve_trailing_spaces.json | 2 +- .../html/check_local_file_against_response.json | 2 +- .../format/html/check_response_format_html.json | 2 +- .../xhtml/check_local_file_against_response.json | 2 +- .../xhtml/check_response_format_xhtml.json | 2 +- .../xml/check_local_file_against_response.json | 2 +- .../xml/check_response_against_local_file.json | 2 +- .../format/xml/check_response_format_xml.json | 2 +- .../format/xml/check_response_format_xml2.json | 2 +- .../format/xml/compare_exiftool_with_xml.json | 2 +- test/response/preprocess/load_static_files.json | 4 ++-- .../preprocess_bounce_exiftool_json.json | 2 +- .../preprocess_file_exiftool_json.json | 2 +- .../preprocess/preprocess_file_exiftool_xml.json | 2 +- 18 files changed, 35 insertions(+), 35 deletions(-) diff --git a/test/proxy/read_from_proxies.json b/test/proxy/read_from_proxies.json index bab82a6b..e964a4c0 100644 --- a/test/proxy/read_from_proxies.json +++ b/test/proxy/read_from_proxies.json @@ -4,7 +4,7 @@ { "name": "Poll 'test' proxy store, offset {{ $offset }}", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxyread/test", "method": "GET", "query_params": { @@ -32,12 +32,12 @@ ,{ "name": "Poll 'test' proxy store for binary content (image)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxyread/test", "method": "GET", "query_params": { - "offset": "{{ datastore "max_entries" }}" - } + "offset": "{{ datastore `max_entries` }}" + } }, "response": { "header": { @@ -59,7 +59,7 @@ { "name": "Poll 'test2' proxy store, offset {{ $offset }}", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxyread/test2", "method": "GET", "query_params": { @@ -72,7 +72,7 @@ "X-Apitest-Proxy-Request-Path": ["/proxywrite/test2"], "X-Apitest-Proxy-Request-Query": ["is=here&my=data_{{ $offset }}&some=value"], "X-Some": ["x-header"], - "X-Apitest-Proxy-Store-Count": ["{{ datastore "max_entries" }}"], + "X-Apitest-Proxy-Store-Count": ["{{ datastore `max_entries` }}"], {{ $nextOffset := add $offset 1 }} {{ if gt $nextOffset (subtract 1 (datastore "max_entries")) }} {{ $nextOffset = 0 }} diff --git a/test/proxy/write_to_proxies.json b/test/proxy/write_to_proxies.json index 0c05d843..27347528 100644 --- a/test/proxy/write_to_proxies.json +++ b/test/proxy/write_to_proxies.json @@ -4,7 +4,7 @@ { "name": "Add some JSON requests to proxy 'test'", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxywrite/test", "method": "POST", "query_params": { @@ -31,7 +31,7 @@ ,{ "name": "post image to proxy 'test'", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxywrite/test", "method": "POST", "body_file": "@../_res/assets/camera.jpg", @@ -51,7 +51,7 @@ ,{ "name": "Add some JSON requests to proxy 'test2'", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "proxywrite/test2", "method": "GET", "query_params": { diff --git a/test/response/format/csv/check_store_values.json b/test/response/format/csv/check_store_values.json index bef04791..5cbb4a0f 100644 --- a/test/response/format/csv/check_store_values.json +++ b/test/response/format/csv/check_store_values.json @@ -2,7 +2,7 @@ { "name": "Get existing CSV file, checking response with previously stored data", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, @@ -13,8 +13,8 @@ }, "body": [ { - "name": "{{ datastore "name" }}", - "extension": "{{ datastore "ext" }}" + "name": {{ datastore "name" | marshal }}, + "extension": {{ datastore "ext" | marshal }} } ] } diff --git a/test/response/format/csv/csv_requests.json b/test/response/format/csv/csv_requests.json index 1502d434..50e7936e 100644 --- a/test/response/format/csv/csv_requests.json +++ b/test/response/format/csv/csv_requests.json @@ -2,7 +2,7 @@ { "name": "Get non-existing CSV file (expecting statuscode)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "wrong/path.jpg", "method": "GET" }, @@ -13,7 +13,7 @@ { "name": "Get non-existing CSV file (not expecting statuscode, so should fail)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "wrong/path.jpg", "method": "GET" }, @@ -22,7 +22,7 @@ { "name": "Get existing CSV file", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, @@ -33,7 +33,7 @@ { "name": "Get existing CSV file, without checking response", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" } @@ -41,7 +41,7 @@ { "name": "Get existing CSV file, checking response and storing data (fail - no format)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, @@ -56,7 +56,7 @@ { "name": "Get existing CSV file, checking response and storing data (success)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, @@ -73,7 +73,7 @@ { "name": "Get existing CSV file, only storing data (fail - no format)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, @@ -85,7 +85,7 @@ { "name": "Get existing CSV file, only storing data (success)", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "dummy.csv", "method": "GET" }, diff --git a/test/response/format/csv/preserve_trailing_spaces.json b/test/response/format/csv/preserve_trailing_spaces.json index 8af36368..b21edf6f 100644 --- a/test/response/format/csv/preserve_trailing_spaces.json +++ b/test/response/format/csv/preserve_trailing_spaces.json @@ -1,7 +1,7 @@ { "name": "Get existing CSV file with trailing spaces in values of type \"string,array\"", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce-json", "method": "POST", "body": {{ file_csv "trailing_spaces.csv" ',' | marshal }} diff --git a/test/response/format/html/check_local_file_against_response.json b/test/response/format/html/check_local_file_against_response.json index 144ec2fc..519eed3a 100644 --- a/test/response/format/html/check_local_file_against_response.json +++ b/test/response/format/html/check_local_file_against_response.json @@ -2,7 +2,7 @@ { "name": "Get existing HTML file", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce-json", "method": "POST", "body": {{ file_html2json "sample.html" }} diff --git a/test/response/format/html/check_response_format_html.json b/test/response/format/html/check_response_format_html.json index 84f4fa85..021c6126 100644 --- a/test/response/format/html/check_response_format_html.json +++ b/test/response/format/html/check_response_format_html.json @@ -1,7 +1,7 @@ { "name": "get sample.html from test server, use response format \"html\"", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "sample.html", "method": "GET" }, diff --git a/test/response/format/xhtml/check_local_file_against_response.json b/test/response/format/xhtml/check_local_file_against_response.json index 0e6e1e79..7df4c673 100644 --- a/test/response/format/xhtml/check_local_file_against_response.json +++ b/test/response/format/xhtml/check_local_file_against_response.json @@ -2,7 +2,7 @@ { "name": "Get existing XHTML file", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce-json", "method": "POST", "body": {{ file_xhtml2json "sample.xhtml" }} diff --git a/test/response/format/xhtml/check_response_format_xhtml.json b/test/response/format/xhtml/check_response_format_xhtml.json index e8660d93..c8b01818 100644 --- a/test/response/format/xhtml/check_response_format_xhtml.json +++ b/test/response/format/xhtml/check_response_format_xhtml.json @@ -1,7 +1,7 @@ { "name": "bounce XHTML file, use response format \"xhtml\"", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce", "method": "POST", "body": { diff --git a/test/response/format/xml/check_local_file_against_response.json b/test/response/format/xml/check_local_file_against_response.json index 72bc8a65..b370304f 100644 --- a/test/response/format/xml/check_local_file_against_response.json +++ b/test/response/format/xml/check_local_file_against_response.json @@ -1,7 +1,7 @@ { "name": "Get existing XML file", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce-json", "method": "POST", "body": {{ file_xml2json "sample.xml" }} diff --git a/test/response/format/xml/check_response_against_local_file.json b/test/response/format/xml/check_response_against_local_file.json index b1012048..a93eb10f 100644 --- a/test/response/format/xml/check_response_against_local_file.json +++ b/test/response/format/xml/check_response_against_local_file.json @@ -1,7 +1,7 @@ { "name": "Get existing XML file", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce-json", "method": "POST", "body": {{ file "result_xml2.json" }} diff --git a/test/response/format/xml/check_response_format_xml.json b/test/response/format/xml/check_response_format_xml.json index 7a0a9eaf..caec4415 100644 --- a/test/response/format/xml/check_response_format_xml.json +++ b/test/response/format/xml/check_response_format_xml.json @@ -1,7 +1,7 @@ { "name": "bounce xml file, use response format \"xml\"", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce", "method": "POST", "body": { diff --git a/test/response/format/xml/check_response_format_xml2.json b/test/response/format/xml/check_response_format_xml2.json index 3066137d..2f368700 100644 --- a/test/response/format/xml/check_response_format_xml2.json +++ b/test/response/format/xml/check_response_format_xml2.json @@ -1,7 +1,7 @@ { "name": "bounce xml file, use response format \"xml2\"", "request": { - "server_url": "{{ datastore "req_base_url" }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "bounce", "method": "POST", "body": { diff --git a/test/response/format/xml/compare_exiftool_with_xml.json b/test/response/format/xml/compare_exiftool_with_xml.json index 3e90d494..910035a4 100644 --- a/test/response/format/xml/compare_exiftool_with_xml.json +++ b/test/response/format/xml/compare_exiftool_with_xml.json @@ -2,7 +2,7 @@ "name": "preprocess asset berlin.jpg with exiftool in xml format, compare \"exiftool_result.xml\"", "request": { // load static file - "server_url": "{{ datastore `req_base_url` }}", + "server_url": {{ datastore "req_base_url" | marshal }}, "endpoint": "berlin.jpg", "method": "GET" }, diff --git a/test/response/preprocess/load_static_files.json b/test/response/preprocess/load_static_files.json index d0619094..4474cee9 100644 --- a/test/response/preprocess/load_static_files.json +++ b/test/response/preprocess/load_static_files.json @@ -2,7 +2,7 @@ { "name": "test static endpoint (invalid file path)", "request": { - "server_url": "{{ datastore "local_url" }}", + "server_url": {{ datastore "local_url" | marshal }}, "endpoint": "wrong/path.jpg", "method": "GET" }, @@ -13,7 +13,7 @@ { "name": "test static endpoint (serve static file relative to server directory)", "request": { - "server_url": "{{ datastore "local_url" }}", + "server_url": {{ datastore "local_url" | marshal }}, "endpoint": "camera.jpg", "method": "GET" }, diff --git a/test/response/preprocess/preprocess_bounce_exiftool_json.json b/test/response/preprocess/preprocess_bounce_exiftool_json.json index 3beaf795..afe077eb 100644 --- a/test/response/preprocess/preprocess_bounce_exiftool_json.json +++ b/test/response/preprocess/preprocess_bounce_exiftool_json.json @@ -2,7 +2,7 @@ { "name": "bounce (binary), pre_process exiftool", "request": { - "server_url": "{{ datastore "local_url" }}", + "server_url": {{ datastore "local_url" | marshal }}, "endpoint": "bounce", "method": "POST", "query_params": { diff --git a/test/response/preprocess/preprocess_file_exiftool_json.json b/test/response/preprocess/preprocess_file_exiftool_json.json index 8c99224e..42abe634 100644 --- a/test/response/preprocess/preprocess_file_exiftool_json.json +++ b/test/response/preprocess/preprocess_file_exiftool_json.json @@ -3,7 +3,7 @@ "name": "preprocess asset camera.jpg with exiftool in json format", "request": { // load static file - "server_url": "{{ datastore "local_url" }}", + "server_url": {{ datastore "local_url" | marshal }}, "endpoint": "camera.jpg", "method": "GET" }, diff --git a/test/response/preprocess/preprocess_file_exiftool_xml.json b/test/response/preprocess/preprocess_file_exiftool_xml.json index 55c58499..95b21187 100644 --- a/test/response/preprocess/preprocess_file_exiftool_xml.json +++ b/test/response/preprocess/preprocess_file_exiftool_xml.json @@ -3,7 +3,7 @@ "name": "preprocess asset camera.jpg with exiftool in xml format", "request": { // load static file - "server_url": "{{ datastore "local_url" }}", + "server_url": {{ datastore "local_url" | marshal }}, "endpoint": "camera.jpg", "method": "GET" }, From 57b11495453183292792aef2d5ebb2969ffeff95 Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Fri, 11 Jul 2025 14:19:13 +0200 Subject: [PATCH 2/7] new response format: text; renders the response body into a json string; see #76678 --- pkg/lib/api/response.go | 17 +++++-- test/_res/assets/dummy.xml | 12 +++++ test/response/format/text/csv_requests.json | 46 +++++++++++++++++++ test/response/format/text/manifest.json | 16 +++++++ test/response/format/text/xml_requests.json | 50 +++++++++++++++++++++ 5 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 test/_res/assets/dummy.xml create mode 100644 test/response/format/text/csv_requests.json create mode 100644 test/response/format/text/manifest.json create mode 100644 test/response/format/text/xml_requests.json diff --git a/pkg/lib/api/response.go b/pkg/lib/api/response.go index d54efcff..9d6becf8 100755 --- a/pkg/lib/api/response.go +++ b/pkg/lib/api/response.go @@ -67,7 +67,7 @@ type responseSerializationInternal struct { type ResponseFormat struct { IgnoreBody bool `json:"-"` // if true, do not try to parse the body (since it is not expected in the response) - Type string `json:"type"` // default "json", allowed: "csv", "json", "xml", "xml2", "html", "xhtml", "binary" + Type string `json:"type"` // default "json", allowed: "csv", "json", "xml", "xml2", "html", "xhtml", "binary", "text" CSV struct { Comma string `json:"comma,omitempty"` } `json:"csv,omitempty"` @@ -226,13 +226,22 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm // We have another file format (binary). We thereby take the md5 Hash of the body and compare that one hasher := md5.New() hasher.Write([]byte(resp.Body)) - JsonObject := util.JsonObject{ + jsonObject := util.JsonObject{ "md5sum": util.JsonString(hex.EncodeToString(hasher.Sum(nil))), } - bodyData, err = json.Marshal(JsonObject) + bodyData, err = json.Marshal(jsonObject) if err != nil { return res, fmt.Errorf("Could not marshal body with md5sum to json: %w", err) } + case "text": + // render the content as text + jsonObject := util.JsonObject{ + "text": util.JsonString(resp.Body), + } + bodyData, err = json.Marshal(jsonObject) + if err != nil { + return res, fmt.Errorf("Could not marshal body to text (string): %w", err) + } case "": // no specific format, we assume a json, and thereby try to unmarshal it into our body bodyData = resp.Body @@ -409,7 +418,7 @@ func (response Response) ToString() string { body := resp.Body switch resp.Format.Type { - case "xml", "xml2", "csv", "html", "xhtml": + case "xml", "xml2", "csv", "html", "xhtml", "text": if utf8.Valid(body) { bodyString, err = resp.ServerResponseToJsonString(true) if err != nil { diff --git a/test/_res/assets/dummy.xml b/test/_res/assets/dummy.xml new file mode 100644 index 00000000..e8077875 --- /dev/null +++ b/test/_res/assets/dummy.xml @@ -0,0 +1,12 @@ + + + yo + pdf + 50 + + + jo + png + 11500 + + diff --git a/test/response/format/text/csv_requests.json b/test/response/format/text/csv_requests.json new file mode 100644 index 00000000..5e681a31 --- /dev/null +++ b/test/response/format/text/csv_requests.json @@ -0,0 +1,46 @@ +[ + { + "name": "Get existing CSV file, check content in csv format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "dummy.csv", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "csv" + }, + "body": [ + { + "extension": "pdf", + "name": "yo", + "size": "50" + }, + { + "extension": "png", + "name": "jo", + "size": "11500" + } + ] + } + }, + { + "name": "Get existing CSV file, check content in text format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "dummy.csv", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "text" + }, + "body": { + // "text": "name,extension,size\nyo,pdf,50\n[...]" + "text": {{ file "../../../_res/assets/dummy.csv" | marshal }} + } + } + } +] \ No newline at end of file diff --git a/test/response/format/text/manifest.json b/test/response/format/text/manifest.json new file mode 100644 index 00000000..c6089bc7 --- /dev/null +++ b/test/response/format/text/manifest.json @@ -0,0 +1,16 @@ +{{ $local_port:=":9999"}} +{ + "http_server": { + "addr": "{{ $local_port }}", + "dir": "../../../_res/assets", + "testmode": false + }, + "store": { + "req_base_url": "http://localhost{{ $local_port }}" + }, + "name": "Text format tests", + "tests": [ + "@csv_requests.json" + ,"@xml_requests.json" + ] +} \ No newline at end of file diff --git a/test/response/format/text/xml_requests.json b/test/response/format/text/xml_requests.json new file mode 100644 index 00000000..9be61aef --- /dev/null +++ b/test/response/format/text/xml_requests.json @@ -0,0 +1,50 @@ +[ + { + "name": "Get existing xml file, check content in xml format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "dummy.xml", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "xml2" + }, + "body": { + "files": { + "file": [ + { + "extension": "pdf", + "name": "yo", + "size": "50" + }, + { + "extension": "png", + "name": "jo", + "size": "11500" + } + ] + } + } + } + }, + { + "name": "Get existing xml file, check content in text format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "dummy.xml", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "text" + }, + "body": { + // "text": "\n \n [...]" + "text": {{ file "../../../_res/assets/dummy.xml" | marshal }} + } + } + } +] \ No newline at end of file From ea14f04c3bcca62b94b8bc33c0f53e116918a3b2 Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Fri, 11 Jul 2025 14:38:15 +0200 Subject: [PATCH 3/7] lot of different small fixes and improvements in reaction to warnings --- api_testcase_test.go | 24 ++++++++++++------------ api_testsuite_test.go | 4 ++-- config_test.go | 22 +++++++++++----------- pkg/lib/api/build_policies_test.go | 2 +- pkg/lib/api/response.go | 6 +++--- pkg/lib/csv/csv.go | 4 +--- pkg/lib/filesystem/util_test.go | 6 +++--- pkg/lib/report/parsing_functions.go | 2 +- pkg/lib/template/template_loader_test.go | 4 ++-- 9 files changed, 36 insertions(+), 38 deletions(-) diff --git a/api_testcase_test.go b/api_testcase_test.go index e9b016c5..1a216f9d 100644 --- a/api_testcase_test.go +++ b/api_testcase_test.go @@ -73,7 +73,7 @@ func TestCollectResponseShouldWork(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() @@ -124,8 +124,8 @@ func TestCollectLoadExternalFile(t *testing.T) { }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) r := report.NewReport() @@ -184,8 +184,8 @@ func TestCollectLoadExternalCollect(t *testing.T) { }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) r := report.NewReport() @@ -283,8 +283,8 @@ func TestCollectEvents(t *testing.T) { }`) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) - afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) + afero.WriteFile(filesystem.Fs, "collect.json", []byte(externalFile), 0644) r := report.NewReport() @@ -336,7 +336,7 @@ func TestCollectResponseShouldFail(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() r.Root().NoLogTime = true @@ -398,7 +398,7 @@ func TestHeaderFromDatastoreWithMap(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() r.Root().NoLogTime = true @@ -449,7 +449,7 @@ func TestHeaderFromDatastoreWithSlice(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() r.Root().NoLogTime = true @@ -507,7 +507,7 @@ func TestCookieSetInDatastore(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() r.Root().NoLogTime = true @@ -603,7 +603,7 @@ func TestCookiesReceivedFromRequest(t *testing.T) { `) filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 644) + afero.WriteFile(filesystem.Fs, "manifest.json", []byte(testManifest), 0644) r := report.NewReport() r.Root().NoLogTime = true diff --git a/api_testsuite_test.go b/api_testsuite_test.go index 192ec910..5fcbf8e4 100644 --- a/api_testsuite_test.go +++ b/api_testsuite_test.go @@ -10,9 +10,9 @@ import ( func TestLoadManifest(t *testing.T) { filesystem.Fs = afero.NewMemMapFs() - afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 644) + afero.WriteFile(filesystem.Fs, "externalFile", []byte(`{"load":{"me":"loaded"}}`), 0644) - afero.WriteFile(filesystem.Fs, "testManifest.json", []byte(`{"testload": {{ file "externalFile" | gjson "load.me"}}}`), 644) + afero.WriteFile(filesystem.Fs, "testManifest.json", []byte(`{"testload": {{ file "externalFile" | gjson "load.me"}}}`), 0644) s := Suite{manifestPath: "testManifest.json"} diff --git a/config_test.go b/config_test.go index 140413e4..586ea220 100644 --- a/config_test.go +++ b/config_test.go @@ -34,18 +34,18 @@ func SetupFS() { //Setup test filesystem filesystem.Fs = afero.NewMemMapFs() - filesystem.Fs.MkdirAll(filepath.Dir(manifestPath1), 755) - filesystem.Fs.MkdirAll(filepath.Dir(manifestPath2), 755) - filesystem.Fs.MkdirAll(filepath.Dir(manifestPath3), 755) - filesystem.Fs.MkdirAll(filepath.Dir(manifestPath4), 755) - filesystem.Fs.MkdirAll(filepath.Dir(manifestPath5), 755) - filesystem.Fs.MkdirAll(filepath.Join("path", "empty"), 755) + filesystem.Fs.MkdirAll(filepath.Dir(manifestPath1), 0755) + filesystem.Fs.MkdirAll(filepath.Dir(manifestPath2), 0755) + filesystem.Fs.MkdirAll(filepath.Dir(manifestPath3), 0755) + filesystem.Fs.MkdirAll(filepath.Dir(manifestPath4), 0755) + filesystem.Fs.MkdirAll(filepath.Dir(manifestPath5), 0755) + filesystem.Fs.MkdirAll(filepath.Join("path", "empty"), 0755) - afero.WriteFile(filesystem.Fs, manifestPath1, []byte(""), 644) - afero.WriteFile(filesystem.Fs, manifestPath2, []byte(""), 644) - afero.WriteFile(filesystem.Fs, manifestPath3, []byte(""), 644) - afero.WriteFile(filesystem.Fs, manifestPath4, []byte(""), 644) - afero.WriteFile(filesystem.Fs, manifestPath5, []byte(""), 644) + afero.WriteFile(filesystem.Fs, manifestPath1, []byte(""), 0644) + afero.WriteFile(filesystem.Fs, manifestPath2, []byte(""), 0644) + afero.WriteFile(filesystem.Fs, manifestPath3, []byte(""), 0644) + afero.WriteFile(filesystem.Fs, manifestPath4, []byte(""), 0644) + afero.WriteFile(filesystem.Fs, manifestPath5, []byte(""), 0644) } diff --git a/pkg/lib/api/build_policies_test.go b/pkg/lib/api/build_policies_test.go index be94ef7a..3384178e 100644 --- a/pkg/lib/api/build_policies_test.go +++ b/pkg/lib/api/build_policies_test.go @@ -44,7 +44,7 @@ func TestBuildMultipart(t *testing.T) { func TestBuildMultipart_ErrPathSpec(t *testing.T) { testRequest := Request{ Body: map[string]any{ - "somekey": fmt.Sprintf("noPathspec"), + "somekey": "noPathspec", }, ManifestDir: "test/path/", } diff --git a/pkg/lib/api/response.go b/pkg/lib/api/response.go index 9d6becf8..280ff507 100755 --- a/pkg/lib/api/response.go +++ b/pkg/lib/api/response.go @@ -62,7 +62,7 @@ type ResponseSerialization struct { type responseSerializationInternal struct { ResponseSerialization - HeaderFlat map[string]any `json:"header_flat,omitemty"` + HeaderFlat map[string]any `json:"header_flat,omitempty"` } type ResponseFormat struct { @@ -425,7 +425,7 @@ func (response Response) ToString() string { bodyString = string(body) } } else { - bodyString = fmt.Sprintf("[BINARY DATA NOT DISPLAYED]\n\n") + bodyString = "[BINARY DATA NOT DISPLAYED]\n\n" } case "binary": resp.Format.IgnoreBody = false @@ -437,7 +437,7 @@ func (response Response) ToString() string { if utf8.Valid(body) { bodyString = string(body) } else { - bodyString = fmt.Sprintf("[BINARY DATA NOT DISPLAYED]\n\n") + bodyString = "[BINARY DATA NOT DISPLAYED]\n\n" } } diff --git a/pkg/lib/csv/csv.go b/pkg/lib/csv/csv.go index 663b2ba4..016fa39c 100644 --- a/pkg/lib/csv/csv.go +++ b/pkg/lib/csv/csv.go @@ -173,9 +173,7 @@ func renderCSV(read io.Reader, comma rune) ([][]string, error) { } func isValidFormat(format string) bool { - if strings.HasPrefix(format, "*") { - format = format[1:] - } + format = strings.TrimPrefix(format, "*") validFormats := []string{"string", "int64", "int", "float64", "bool"} for _, v := range validFormats { if format == v || format == v+",array" || format == "json" { diff --git a/pkg/lib/filesystem/util_test.go b/pkg/lib/filesystem/util_test.go index be27ada8..d58f7446 100644 --- a/pkg/lib/filesystem/util_test.go +++ b/pkg/lib/filesystem/util_test.go @@ -11,10 +11,10 @@ import ( func TestMemFS(t *testing.T) { Fs = afero.NewMemMapFs() - for i := 0; i < 1000; i++ { - Fs.MkdirAll(filepath.Join("store", "test1", "data", strconv.Itoa(i)), 755) + for i := range 1000 { + Fs.MkdirAll(filepath.Join("store", "test1", "data", strconv.Itoa(i)), 0755) } - for i := 0; i < 1000; i++ { + for i := range 1000 { _, err := Fs.Open(filepath.Join("store", "test1", "data", strconv.Itoa(i))) if err != nil { t.Error(err) diff --git a/pkg/lib/report/parsing_functions.go b/pkg/lib/report/parsing_functions.go index 2cd885d8..637eb4a6 100755 --- a/pkg/lib/report/parsing_functions.go +++ b/pkg/lib/report/parsing_functions.go @@ -114,7 +114,7 @@ func ParseJSONStatsResult(baseResult *ReportElement) []byte { } stats.Groups = make([]statsGroup, baseResult.report.StatsGroups) - for i := 0; i < baseResult.report.StatsGroups; i++ { + for i := range baseResult.report.StatsGroups { stats.Groups[i] = statsGroup{ Number: i, Runtime: 0, diff --git a/pkg/lib/template/template_loader_test.go b/pkg/lib/template/template_loader_test.go index 25446f96..eb2bf6d8 100644 --- a/pkg/lib/template/template_loader_test.go +++ b/pkg/lib/template/template_loader_test.go @@ -207,7 +207,7 @@ int64,string,"stringer,array","int64,array"`, ``, true}, } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { - root := []byte(fmt.Sprintf(`{{ file_csv "somefile.json" ',' | marshal }}`)) + root := []byte(`{{ file_csv "somefile.json" ',' | marshal }}`) target := []byte(testCase.csv) @@ -253,7 +253,7 @@ int64,string,"string,array","int64,array" } for i, testCase := range testCases { t.Run(fmt.Sprintf("case_%d", i), func(t *testing.T) { - root := []byte(fmt.Sprintf(`{{ file_csv "somefile.json" ',' | rows_to_map "name" "ages" | marshal }}`)) + root := []byte(`{{ file_csv "somefile.json" ',' | rows_to_map "name" "ages" | marshal }}`) target := []byte(testCase.csv) From 0754a2fa0b518e391d0b51f644a94ab8c8952aca Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Mon, 14 Jul 2025 10:52:43 +0200 Subject: [PATCH 4/7] Update README.md --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 52524b17..71f0c412 100644 --- a/README.md +++ b/README.md @@ -617,6 +617,28 @@ You can also specify the delimiter (`comma`) for the CSV format (default: `,`): } ``` +## Text Data comparison + +If the response format is specified as `"type": "text"`, the content of the response is returned in a JSON object with a single JSON String. + +```json +{ + "name": "Text comparison", + "request": { + "endpoint": "export/1/files/file.csv", + "method": "GET" + }, + "response": { + "format": { + "type": "text" + }, + "body": { + "text": "" + } + } +} +``` + ## Preprocessing responses Responses in arbitrary formats can be preprocessed by calling any command line tool that can produce JSON, XML, CSV or binary output. In combination with the `type` parameter in `format`, non-JSON output can be [formatted after preprocessing](#reading-metadata-from-a-file-xml-format). If the result is already in JSON format, it can be [checked directly](#reading-metadata-from-a-file-json-format). From ae097586cb42d14cd414559118c310e5daf48bb5 Mon Sep 17 00:00:00 2001 From: Martin Rode Date: Tue, 15 Jul 2025 12:58:04 +0200 Subject: [PATCH 5/7] added failing test --- README.md | 2 +- .../format/text/csv_requests_pre_process.json | 25 +++++++++++++++++++ test/response/format/text/manifest.json | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/response/format/text/csv_requests_pre_process.json diff --git a/README.md b/README.md index 71f0c412..cbf14935 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Manifest is loaded as **template**, so you can use variables, Go **range** and * | `response.statuscode` | Expected http [status code](#statuscode). See api documentation for the endpoint to decide which code to expect | | `response.header` | If you expect certain response headers, you can define them here. A single key can have multiple headers | | `response.cookie` | Cookies will be under this key, in a map `name => cookie` | -| `response.format` | Optionally, the expected format of the response can be specified or [preprocessed](#preprocessing-responses) so that it can be converted into json and can be checked. Formats are: [`binary`](#binary-data-comparison), [`xml`](#xml-data-comparison), [`html`](#html-data-comparison), [`csv`](#csv-data-comparison) | +| `response.format` | Optionally, the expected format of the response can be specified or [preprocessed](#preprocessing-responses) so that it can be converted into json and can be checked. Formats are: [`binary`](#binary-data-comparison), [`xml`](#xml-data-comparison), [`html`](#html-data-comparison), [`csv`](#csv-data-comparison), [`text`](#text-data-comparison) | | `response.body` | The body we want to assert on | | `store_response_gjson` | Store parts of the response into the datastore | | `store_response_gjson.sess_cookie` | Cookies are stored in `cookie` map | diff --git a/test/response/format/text/csv_requests_pre_process.json b/test/response/format/text/csv_requests_pre_process.json new file mode 100644 index 00000000..e1baf662 --- /dev/null +++ b/test/response/format/text/csv_requests_pre_process.json @@ -0,0 +1,25 @@ +{ + "name": "Get existing CSV file, check content in text format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "dummy.csv", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "text", + "pre_process": { + "cmd": { + "name": "wc", + "args": [ + "-c" + ] + } + } + }, + "body": { + "text": " 42\n" + } + } +} \ No newline at end of file diff --git a/test/response/format/text/manifest.json b/test/response/format/text/manifest.json index c6089bc7..d1a13bbe 100644 --- a/test/response/format/text/manifest.json +++ b/test/response/format/text/manifest.json @@ -12,5 +12,7 @@ "tests": [ "@csv_requests.json" ,"@xml_requests.json" + // format "text" with pre process + ,"@csv_requests_pre_process.json" ] } \ No newline at end of file From 1d747e0376606400e9f2d47757572db90e9291bb Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Tue, 15 Jul 2025 14:38:38 +0200 Subject: [PATCH 6/7] updated response: - updated format "text": - output more fields with trimmed text, lines, parsed numbers - debug output of responses: - do not apply preprocess see #76678 --- README.md | 32 ++++++++++++---- pkg/lib/api/response.go | 37 ++++++++++++++----- test/_res/assets/number.txt | 1 + test/response/format/text/csv_requests.json | 11 +++++- .../format/text/csv_requests_pre_process.json | 5 ++- test/response/format/text/manifest.json | 1 + test/response/format/text/num_requests.json | 25 +++++++++++++ test/response/format/text/xml_requests.json | 20 +++++++++- 8 files changed, 110 insertions(+), 22 deletions(-) create mode 100644 test/_res/assets/number.txt create mode 100644 test/response/format/text/num_requests.json diff --git a/README.md b/README.md index cbf14935..9ec9f5f2 100644 --- a/README.md +++ b/README.md @@ -619,22 +619,38 @@ You can also specify the delimiter (`comma`) for the CSV format (default: `,`): ## Text Data comparison -If the response format is specified as `"type": "text"`, the content of the response is returned in a JSON object with a single JSON String. +If the response format is specified as `"type": "text"`, the content of the response is returned in a JSON object. + +The object contains the following keys: + +* `text`: the response text without any changes +* `text_trimmed`: the response text, leading and trailing whitespaces have been trimmed +* `lines`: the response text, split into lines +* `float64`: if the text can be parsed into a float64 value, the numerical value is returned, else `null` +* `int64`: if the text can be parsed into a int64 value, the numerical value is returned, else `null` + +Assume we get the content of this text file in the response: + +``` + 42.35 + +``` ```json { "name": "Text comparison", "request": { - "endpoint": "export/1/files/file.csv", + "endpoint": "export/1/files/number.txt", "method": "GET" }, "response": { - "format": { - "type": "text" - }, - "body": { - "text": "" - } + "text": " 42\n", + "text_trimmed": "42", + "lines": [ + "42" + ], + "float64": 42, + "int64": 42, } } ``` diff --git a/pkg/lib/api/response.go b/pkg/lib/api/response.go index 280ff507..512f3c9e 100755 --- a/pkg/lib/api/response.go +++ b/pkg/lib/api/response.go @@ -1,6 +1,7 @@ package api import ( + "bufio" "bytes" "crypto/md5" "encoding/hex" @@ -8,6 +9,7 @@ import ( "fmt" "io" "net/http" + "strconv" "strings" "time" "unicode/utf8" @@ -168,6 +170,16 @@ func NewResponseFromSpec(spec ResponseSerialization) (res Response, err error) { return NewResponse(spec.StatusCode, spec.Headers, cookies, body, spec.BodyControl, spec.Format) } +// splitLines is a helper function needed for format "text" +func splitLines(s string) (lines util.JsonArray) { + lines = util.JsonArray{} + sc := bufio.NewScanner(strings.NewReader(s)) + for sc.Scan() { + lines = append(lines, sc.Text()) + } + return lines +} + // ServerResponseToGenericJSON parse response from server. convert xml, csv, binary to json if necessary func (response Response) ServerResponseToGenericJSON(responseFormat ResponseFormat, bodyOnly bool) (any, error) { var ( @@ -235,9 +247,23 @@ func (response Response) ServerResponseToGenericJSON(responseFormat ResponseForm } case "text": // render the content as text + bodyText := string(resp.Body) + bodyTextTrimmed := strings.TrimSpace(bodyText) jsonObject := util.JsonObject{ - "text": util.JsonString(resp.Body), + "text": util.JsonString(bodyText), + "text_trimmed": util.JsonString(bodyTextTrimmed), + "lines": splitLines(bodyText), + "float64": nil, + "int64": nil, } + // try to parse the string as float and int + // ignore errors silently in case the text is not numerical + n, err2 := strconv.ParseFloat(bodyTextTrimmed, 64) + if err2 == nil { + jsonObject["float64"] = util.JsonNumber(n) + jsonObject["int64"] = util.JsonNumber(int64(n)) + } + bodyData, err = json.Marshal(jsonObject) if err != nil { return res, fmt.Errorf("Could not marshal body to text (string): %w", err) @@ -404,14 +430,7 @@ func (response Response) ToString() string { } } - if response.Format.PreProcess != nil { - resp, err = response.Format.PreProcess.RunPreProcess(response) - if err != nil { - resp = response - } - } else { - resp = response - } + resp = response // for logging, always show the body resp.Format.IgnoreBody = false diff --git a/test/_res/assets/number.txt b/test/_res/assets/number.txt new file mode 100644 index 00000000..9b4c81a7 --- /dev/null +++ b/test/_res/assets/number.txt @@ -0,0 +1 @@ + 42.35 diff --git a/test/response/format/text/csv_requests.json b/test/response/format/text/csv_requests.json index 5e681a31..57ab9520 100644 --- a/test/response/format/text/csv_requests.json +++ b/test/response/format/text/csv_requests.json @@ -38,8 +38,15 @@ "type": "text" }, "body": { - // "text": "name,extension,size\nyo,pdf,50\n[...]" - "text": {{ file "../../../_res/assets/dummy.csv" | marshal }} + "text": "name,extension,size\nyo,pdf,50\njo,png,11500\n", + "text_trimmed": "name,extension,size\nyo,pdf,50\njo,png,11500", + "lines": [ + "name,extension,size", + "yo,pdf,50", + "jo,png,11500" + ], + "int64": null, + "float64": null, } } } diff --git a/test/response/format/text/csv_requests_pre_process.json b/test/response/format/text/csv_requests_pre_process.json index e1baf662..031433bc 100644 --- a/test/response/format/text/csv_requests_pre_process.json +++ b/test/response/format/text/csv_requests_pre_process.json @@ -19,7 +19,10 @@ } }, "body": { - "text": " 42\n" + "text": "43\n", + "text_trimmed": "43", + "float64": 43, + "int64": 43, } } } \ No newline at end of file diff --git a/test/response/format/text/manifest.json b/test/response/format/text/manifest.json index d1a13bbe..a48b7106 100644 --- a/test/response/format/text/manifest.json +++ b/test/response/format/text/manifest.json @@ -12,6 +12,7 @@ "tests": [ "@csv_requests.json" ,"@xml_requests.json" + ,"@num_requests.json" // format "text" with pre process ,"@csv_requests_pre_process.json" ] diff --git a/test/response/format/text/num_requests.json b/test/response/format/text/num_requests.json new file mode 100644 index 00000000..c36dc9aa --- /dev/null +++ b/test/response/format/text/num_requests.json @@ -0,0 +1,25 @@ +[ + { + "name": "Get existing text file with numerical content, check content in text and parsed number format", + "request": { + "server_url": {{ datastore "req_base_url" | marshal }}, + "endpoint": "number.txt", + "method": "GET" + }, + "response": { + "statuscode": 200, + "format": { + "type": "text" + }, + "body": { + "text": " 42.35\n", + "text_trimmed": "42.35", + "lines": [ + " 42.35" + ], + "float64": 42.35, + "int64": 42, + } + } + } +] \ No newline at end of file diff --git a/test/response/format/text/xml_requests.json b/test/response/format/text/xml_requests.json index 9be61aef..2ae58bf2 100644 --- a/test/response/format/text/xml_requests.json +++ b/test/response/format/text/xml_requests.json @@ -42,8 +42,24 @@ "type": "text" }, "body": { - // "text": "\n \n [...]" - "text": {{ file "../../../_res/assets/dummy.xml" | marshal }} + "text": "\n \n yo\n pdf\n 50\n \n \n jo\n png\n 11500\n \n\n", + "text_trimmed": "\n \n yo\n pdf\n 50\n \n \n jo\n png\n 11500\n \n", + "lines": [ + "", + " ", + " yo", + " pdf", + " 50", + " ", + " ", + " jo", + " png", + " 11500", + " ", + "" + ], + "int64": null, + "float64": null, } } } From 6086c624f71f6883f4a8ff2209f041823994e3f9 Mon Sep 17 00:00:00 2001 From: Philipp Hempel Date: Tue, 15 Jul 2025 14:45:43 +0200 Subject: [PATCH 7/7] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9ec9f5f2..8f8235a3 100644 --- a/README.md +++ b/README.md @@ -629,7 +629,7 @@ The object contains the following keys: * `float64`: if the text can be parsed into a float64 value, the numerical value is returned, else `null` * `int64`: if the text can be parsed into a int64 value, the numerical value is returned, else `null` -Assume we get the content of this text file in the response: +Assume we get the content of this text file in the response, including whitespaces and newlines: ``` 42.35 @@ -644,12 +644,12 @@ Assume we get the content of this text file in the response: "method": "GET" }, "response": { - "text": " 42\n", - "text_trimmed": "42", + "text": " 42.35\n", + "text_trimmed": "42.35", "lines": [ - "42" + " 42" ], - "float64": 42, + "float64": 42.35, "int64": 42, } }