Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -617,6 +617,44 @@ 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.

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, including whitespaces and newlines:

```
42.35

```

```json
{
"name": "Text comparison",
"request": {
"endpoint": "export/1/files/number.txt",
"method": "GET"
},
"response": {
"text": " 42.35\n",
"text_trimmed": "42.35",
"lines": [
" 42"
],
"float64": 42.35,
"int64": 42,
}
}
```

## 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).
Expand Down
24 changes: 12 additions & 12 deletions api_testcase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions api_testsuite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}

Expand Down
22 changes: 11 additions & 11 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

}

Expand Down
2 changes: 1 addition & 1 deletion pkg/lib/api/build_policies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
}
Expand Down
58 changes: 43 additions & 15 deletions pkg/lib/api/response.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package api

import (
"bufio"
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"unicode/utf8"
Expand Down Expand Up @@ -62,12 +64,12 @@ 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 {
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"`
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -226,13 +238,36 @@ 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
bodyText := string(resp.Body)
bodyTextTrimmed := strings.TrimSpace(bodyText)
jsonObject := util.JsonObject{
"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)
}
case "":
// no specific format, we assume a json, and thereby try to unmarshal it into our body
bodyData = resp.Body
Expand Down Expand Up @@ -395,28 +430,21 @@ 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

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 {
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
Expand All @@ -428,7 +456,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"
}
}

Expand Down
4 changes: 1 addition & 3 deletions pkg/lib/csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
6 changes: 3 additions & 3 deletions pkg/lib/filesystem/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/lib/report/parsing_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions pkg/lib/template/template_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
12 changes: 12 additions & 0 deletions test/_res/assets/dummy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<files>
<file>
<name>yo</name>
<extension>pdf</extension>
<size>50</size>
</file>
<file>
<name>jo</name>
<extension>png</extension>
<size>11500</size>
</file>
</files>
1 change: 1 addition & 0 deletions test/_res/assets/number.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42.35
Loading
Loading